Initial work
This commit is contained in:
parent
4879210fba
commit
d7412d7191
59
SegmentRouter.php
Normal file
59
SegmentRouter.php
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
class SegmentRouter
|
||||
{
|
||||
private array $routes = [];
|
||||
|
||||
public function add(string $method, string $route, callable $handler)
|
||||
{
|
||||
// Expand the route into segments
|
||||
$segments = explode('/', trim($route, '/'));
|
||||
|
||||
// Push each segment into the routes array as a node
|
||||
$node = &$this->routes;
|
||||
foreach ($segments as $segment) $node = &$node[$segment];
|
||||
|
||||
// Add the handler to the last node
|
||||
$node[$method] = $handler;
|
||||
}
|
||||
|
||||
public function lookup(string $method, string $uri): int
|
||||
{
|
||||
// Expand the URI into segments
|
||||
$uriSegments = explode('/', trim($uri, '/'));
|
||||
$node = $this->routes;
|
||||
$params = [];
|
||||
|
||||
// Traverse the routes array to find the handler
|
||||
foreach ($uriSegments as $segment) {
|
||||
// Check if the segment exists in the node, or if there's a dynamic segment
|
||||
if (!isset($node[$segment])) {
|
||||
$dynamicSegment = $this->matchDynamicSegment($node, $segment);
|
||||
if ($dynamicSegment) {
|
||||
$params[] = $segment;
|
||||
$node = $node[$dynamicSegment];
|
||||
} else {
|
||||
return 404;
|
||||
}
|
||||
} else {
|
||||
$node = $node[$segment];
|
||||
}
|
||||
}
|
||||
|
||||
// If the HTTP method is not supported, return 405
|
||||
if (!isset($node[$method])) return 405;
|
||||
|
||||
// Call the handler
|
||||
$handler = $node[$method];
|
||||
call_user_func_array($handler, $params);
|
||||
return 200;
|
||||
}
|
||||
|
||||
private function matchDynamicSegment(array $node, string $segment)
|
||||
{
|
||||
foreach ($node as $key => $value) {
|
||||
if (strpos($key, ':') === 0) return $key;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
71
TrieRouter.php
Normal file
71
TrieRouter.php
Normal file
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
class TrieRouter
|
||||
{
|
||||
private array $GET = [];
|
||||
private array $POST = [];
|
||||
private array $PUT = [];
|
||||
private array $DELETE = [];
|
||||
|
||||
// Add route to the trie
|
||||
public function add(string $method, string $route, callable $handler)
|
||||
{
|
||||
$node = &$this->{strtoupper($method)};
|
||||
$segments = explode('/', trim($route, '/'));
|
||||
|
||||
foreach ($segments as $segment) {
|
||||
if (!isset($node[$segment])) {
|
||||
$node[$segment] = ['_children' => [], '_handler' => null];
|
||||
}
|
||||
$node = &$node[$segment]['_children'];
|
||||
}
|
||||
|
||||
$node['_handler'] = $handler;
|
||||
}
|
||||
|
||||
// Find and handle the route
|
||||
public function handleRequest(string $method, string $uri)
|
||||
{
|
||||
$node = &$this->{strtoupper($method)};
|
||||
$segments = explode('/', trim($uri, '/'));
|
||||
$params = [];
|
||||
|
||||
foreach ($segments as $segment) {
|
||||
if (isset($node[$segment])) {
|
||||
$node = &$node[$segment]['_children'];
|
||||
} else {
|
||||
// Handle dynamic parameters (e.g., :id)
|
||||
$dynamicSegment = $this->matchDynamicSegment($node, $segment);
|
||||
if ($dynamicSegment) {
|
||||
$params[] = $segment;
|
||||
$node = &$node[$dynamicSegment]['_children'];
|
||||
} else {
|
||||
return $this->notFound();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a handler exists for the current node
|
||||
if (isset($node['_handler'])) {
|
||||
return call_user_func_array($node['_handler'], $params);
|
||||
}
|
||||
|
||||
return $this->notFound();
|
||||
}
|
||||
|
||||
// Match dynamic route segments like ':id'
|
||||
private function matchDynamicSegment(array $node, string $segment)
|
||||
{
|
||||
foreach ($node as $key => $value) {
|
||||
if (strpos($key, ':') === 0) return $key;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Default 404 handler
|
||||
private function notFound()
|
||||
{
|
||||
echo "404 Not Found";
|
||||
return false;
|
||||
}
|
||||
}
|
61
tests/array.php
Normal file
61
tests/array.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
This test fiel puts PHP's array to the test by running a million lookups on a handful of routes.
|
||||
The routes are read from two files, blog.txt and github.txt, and added to two separate arrays.
|
||||
The lookups are then run in two separate loops, one for each array.
|
||||
Each lookup is timed and the memory usage is also printed out at regular intervals.
|
||||
The requests are randomly picked from the array of routes.
|
||||
*/
|
||||
|
||||
function readAndAddRoutes(string $file): array
|
||||
{
|
||||
$array = [];
|
||||
$routes = file($file);
|
||||
foreach ($routes as $route) {
|
||||
[$method, $path] = explode(' ', $route);
|
||||
$path = trim($path);
|
||||
$array[] = [$method, $path, function() {
|
||||
return true;
|
||||
}];
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
$blog = readAndAddRoutes('blog.txt');
|
||||
$github = readAndAddRoutes('github.txt');
|
||||
|
||||
function echoMemoryAndTime(int $i, float $start) {
|
||||
echo "($i lookups) M: " . round(memory_get_usage() / 1024, 1) . "kb - T: ". number_format(microtime(true) - $start, 10) ." seconds\n";
|
||||
}
|
||||
|
||||
function runIterations(int $iterations, array $routes) {
|
||||
echo "\n";
|
||||
echo "===============================================================\n";
|
||||
echo "\n";
|
||||
echo "Running $iterations iterations\n";
|
||||
$start = microtime(true);
|
||||
$interval = $iterations / 10;
|
||||
for ($i = 0; $i < $iterations; $i++) {
|
||||
// pick a random route from the array
|
||||
[$method, $uri, $handler] = $routes[array_rand($routes)];
|
||||
$res = $handler();
|
||||
if ($i !== 0 && $i % ($interval) === 0) echoMemoryAndTime($i, $start);
|
||||
}
|
||||
echo "Time taken: " . number_format(microtime(true) - $start, 10) . " seconds\n";
|
||||
// echo the average time per request
|
||||
echo "Average time per lookup: " . number_format((microtime(true) - $start) / $iterations, 10) . " seconds\n";
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
echo "Starting blog lookups\n";
|
||||
runIterations(100000, $blog);
|
||||
runIterations(1000000, $blog);
|
||||
|
||||
echo "\n";
|
||||
echo "===============================================================\n";
|
||||
echo "\n";
|
||||
echo "Starting github lookups\n";
|
||||
runIterations(10000, $github);
|
||||
runIterations(100000, $github);
|
||||
runIterations(1000000, $github);
|
1000
tests/big.txt
Normal file
1000
tests/big.txt
Normal file
File diff suppressed because it is too large
Load Diff
4
tests/blog.txt
Normal file
4
tests/blog.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
GET /
|
||||
GET /:slug
|
||||
GET /tags
|
||||
GET /tag/:tag
|
203
tests/github.txt
Normal file
203
tests/github.txt
Normal file
|
@ -0,0 +1,203 @@
|
|||
GET /authorizations
|
||||
GET /authorizations/:id
|
||||
POST /authorizations
|
||||
DELETE /authorizations/:id
|
||||
GET /applications/:client_id/tokens/:access_token
|
||||
DELETE /applications/:client_id/tokens
|
||||
DELETE /applications/:client_id/tokens/:access_token
|
||||
GET /events
|
||||
GET /repos/:owner/:repo/events
|
||||
GET /networks/:owner/:repo/events
|
||||
GET /orgs/:org/events
|
||||
GET /users/:user/received_events
|
||||
GET /users/:user/received_events/public
|
||||
GET /users/:user/events
|
||||
GET /users/:user/events/public
|
||||
GET /users/:user/events/orgs/:org
|
||||
GET /feeds
|
||||
GET /notifications
|
||||
GET /repos/:owner/:repo/notifications
|
||||
PUT /notifications
|
||||
PUT /repos/:owner/:repo/notifications
|
||||
GET /notifications/threads/:id
|
||||
GET /notifications/threads/:id/subscription
|
||||
PUT /notifications/threads/:id/subscription
|
||||
DELETE /notifications/threads/:id/subscription
|
||||
GET /repos/:owner/:repo/stargazers
|
||||
GET /users/:user/starred
|
||||
GET /user/starred
|
||||
GET /user/starred/:owner/:repo
|
||||
PUT /user/starred/:owner/:repo
|
||||
DELETE /user/starred/:owner/:repo
|
||||
GET /repos/:owner/:repo/subscribers
|
||||
GET /users/:user/subscriptions
|
||||
GET /user/subscriptions
|
||||
GET /repos/:owner/:repo/subscription
|
||||
PUT /repos/:owner/:repo/subscription
|
||||
DELETE /repos/:owner/:repo/subscription
|
||||
GET /user/subscriptions/:owner/:repo
|
||||
PUT /user/subscriptions/:owner/:repo
|
||||
DELETE /user/subscriptions/:owner/:repo
|
||||
GET /users/:user/gists
|
||||
GET /gists
|
||||
GET /gists/:id
|
||||
POST /gists
|
||||
PUT /gists/:id/star
|
||||
DELETE /gists/:id/star
|
||||
GET /gists/:id/star
|
||||
POST /gists/:id/forks
|
||||
DELETE /gists/:id
|
||||
GET /repos/:owner/:repo/git/blobs/:sha
|
||||
POST /repos/:owner/:repo/git/blobs
|
||||
GET /repos/:owner/:repo/git/commits/:sha
|
||||
POST /repos/:owner/:repo/git/commits
|
||||
GET /repos/:owner/:repo/git/refs
|
||||
POST /repos/:owner/:repo/git/refs
|
||||
GET /repos/:owner/:repo/git/tags/:sha
|
||||
POST /repos/:owner/:repo/git/tags
|
||||
GET /repos/:owner/:repo/git/trees/:sha
|
||||
POST /repos/:owner/:repo/git/trees
|
||||
GET /issues
|
||||
GET /user/issues
|
||||
GET /orgs/:org/issues
|
||||
GET /repos/:owner/:repo/issues
|
||||
GET /repos/:owner/:repo/issues/:number
|
||||
POST /repos/:owner/:repo/issues
|
||||
GET /repos/:owner/:repo/assignees
|
||||
GET /repos/:owner/:repo/assignees/:assignee
|
||||
GET /repos/:owner/:repo/issues/:number/comments
|
||||
POST /repos/:owner/:repo/issues/:number/comments
|
||||
GET /repos/:owner/:repo/issues/:number/events
|
||||
GET /repos/:owner/:repo/labels
|
||||
GET /repos/:owner/:repo/labels/:name
|
||||
POST /repos/:owner/:repo/labels
|
||||
DELETE /repos/:owner/:repo/labels/:name
|
||||
GET /repos/:owner/:repo/issues/:number/labels
|
||||
POST /repos/:owner/:repo/issues/:number/labels
|
||||
DELETE /repos/:owner/:repo/issues/:number/labels/:name
|
||||
PUT /repos/:owner/:repo/issues/:number/labels
|
||||
DELETE /repos/:owner/:repo/issues/:number/labels
|
||||
GET /repos/:owner/:repo/milestones/:number/labels
|
||||
GET /repos/:owner/:repo/milestones
|
||||
GET /repos/:owner/:repo/milestones/:number
|
||||
POST /repos/:owner/:repo/milestones
|
||||
DELETE /repos/:owner/:repo/milestones/:number
|
||||
GET /emojis
|
||||
GET /gitignore/templates
|
||||
GET /gitignore/templates/:name
|
||||
POST /markdown
|
||||
POST /markdown/raw
|
||||
GET /meta
|
||||
GET /rate_limit
|
||||
GET /users/:user/orgs
|
||||
GET /user/orgs
|
||||
GET /orgs/:org
|
||||
GET /orgs/:org/members
|
||||
GET /orgs/:org/members/:user
|
||||
DELETE /orgs/:org/members/:user
|
||||
GET /orgs/:org/public_members
|
||||
GET /orgs/:org/public_members/:user
|
||||
PUT /orgs/:org/public_members/:user
|
||||
DELETE /orgs/:org/public_members/:user
|
||||
GET /orgs/:org/teams
|
||||
GET /teams/:id
|
||||
POST /orgs/:org/teams
|
||||
DELETE /teams/:id
|
||||
GET /teams/:id/members
|
||||
GET /teams/:id/members/:user
|
||||
PUT /teams/:id/members/:user
|
||||
DELETE /teams/:id/members/:user
|
||||
GET /teams/:id/repos
|
||||
GET /teams/:id/repos/:owner/:repo
|
||||
PUT /teams/:id/repos/:owner/:repo
|
||||
DELETE /teams/:id/repos/:owner/:repo
|
||||
GET /user/teams
|
||||
GET /repos/:owner/:repo/pulls
|
||||
GET /repos/:owner/:repo/pulls/:number
|
||||
POST /repos/:owner/:repo/pulls
|
||||
GET /repos/:owner/:repo/pulls/:number/commits
|
||||
GET /repos/:owner/:repo/pulls/:number/files
|
||||
GET /repos/:owner/:repo/pulls/:number/merge
|
||||
PUT /repos/:owner/:repo/pulls/:number/merge
|
||||
GET /repos/:owner/:repo/pulls/:number/comments
|
||||
PUT /repos/:owner/:repo/pulls/:number/comments
|
||||
GET /user/repos
|
||||
GET /users/:user/repos
|
||||
GET /orgs/:org/repos
|
||||
GET /repositories
|
||||
POST /user/repos
|
||||
POST /orgs/:org/repos
|
||||
GET /repos/:owner/:repo
|
||||
GET /repos/:owner/:repo/contributors
|
||||
GET /repos/:owner/:repo/languages
|
||||
GET /repos/:owner/:repo/teams
|
||||
GET /repos/:owner/:repo/tags
|
||||
GET /repos/:owner/:repo/branches
|
||||
GET /repos/:owner/:repo/branches/:branch
|
||||
DELETE /repos/:owner/:repo
|
||||
GET /repos/:owner/:repo/collaborators
|
||||
GET /repos/:owner/:repo/collaborators/:user
|
||||
PUT /repos/:owner/:repo/collaborators/:user
|
||||
DELETE /repos/:owner/:repo/collaborators/:user
|
||||
GET /repos/:owner/:repo/comments
|
||||
GET /repos/:owner/:repo/commits/:sha/comments
|
||||
POST /repos/:owner/:repo/commits/:sha/comments
|
||||
GET /repos/:owner/:repo/comments/:id
|
||||
DELETE /repos/:owner/:repo/comments/:id
|
||||
GET /repos/:owner/:repo/commits
|
||||
GET /repos/:owner/:repo/commits/:sha
|
||||
GET /repos/:owner/:repo/readme
|
||||
GET /repos/:owner/:repo/keys
|
||||
GET /repos/:owner/:repo/keys/:id
|
||||
POST /repos/:owner/:repo/keys
|
||||
DELETE /repos/:owner/:repo/keys/:id
|
||||
GET /repos/:owner/:repo/downloads
|
||||
GET /repos/:owner/:repo/downloads/:id
|
||||
DELETE /repos/:owner/:repo/downloads/:id
|
||||
GET /repos/:owner/:repo/forks
|
||||
POST /repos/:owner/:repo/forks
|
||||
GET /repos/:owner/:repo/hooks
|
||||
GET /repos/:owner/:repo/hooks/:id
|
||||
POST /repos/:owner/:repo/hooks
|
||||
POST /repos/:owner/:repo/hooks/:id/tests
|
||||
DELETE /repos/:owner/:repo/hooks/:id
|
||||
POST /repos/:owner/:repo/merges
|
||||
GET /repos/:owner/:repo/releases
|
||||
GET /repos/:owner/:repo/releases/:id
|
||||
POST /repos/:owner/:repo/releases
|
||||
DELETE /repos/:owner/:repo/releases/:id
|
||||
GET /repos/:owner/:repo/releases/:id/assets
|
||||
GET /repos/:owner/:repo/stats/contributors
|
||||
GET /repos/:owner/:repo/stats/commit_activity
|
||||
GET /repos/:owner/:repo/stats/code_frequency
|
||||
GET /repos/:owner/:repo/stats/participation
|
||||
GET /repos/:owner/:repo/stats/punch_card
|
||||
GET /repos/:owner/:repo/statuses/:ref
|
||||
POST /repos/:owner/:repo/statuses/:ref
|
||||
GET /search/repositories
|
||||
GET /search/code
|
||||
GET /search/issues
|
||||
GET /search/users
|
||||
GET /legacy/issues/search/:owner/:repository/:state/:keyword
|
||||
GET /legacy/repos/search/:keyword
|
||||
GET /legacy/user/search/:keyword
|
||||
GET /legacy/user/email/:email
|
||||
GET /users/:user
|
||||
GET /user
|
||||
GET /users
|
||||
GET /user/emails
|
||||
POST /user/emails
|
||||
DELETE /user/emails
|
||||
GET /users/:user/followers
|
||||
GET /user/followers
|
||||
GET /users/:user/following
|
||||
GET /user/following
|
||||
GET /user/following/:user
|
||||
GET /users/:user/following/:target_user
|
||||
PUT /user/following/:user
|
||||
DELETE /user/following/:user
|
||||
GET /users/:user/keys
|
||||
GET /user/keys
|
||||
GET /user/keys/:id
|
||||
POST /user/keys
|
||||
DELETE /user/keys/:id
|
45
tests/makeRoutes.php
Normal file
45
tests/makeRoutes.php
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
$routes = [];
|
||||
|
||||
$methods = ['GET', 'POST', 'PUT', 'DELETE'];
|
||||
$apis = ['blog', 'github', 'dragonknight', 'ecchi', 'hentai', 'harem', 'isekai', 'mecha', 'romance', 'shoujo', 'shounen', 'slice-of-life', 'supernatural', 'yuri'];
|
||||
$params = ['id', 'slug', 'page', 'extra', 'foo', 'string', 'number', 'bar', 'baz', 'qux', 'quux', 'corge', 'grault', 'garply', 'waldo', 'fred', 'plugh', 'xyzzy', 'thud'];
|
||||
$endpoint = ['edit', 'create', 'delete', 'view', 'change', 'modify', 'generate', 'lift', 'lower', 'raise', 'drop', 'pick', 'choose', 'select', 'deselect', 'unselect', 'reselect', 'pick', 'unpick', 'repick', 'reselect', 'reunpick', 'rechoose', 'reselect'];
|
||||
$midpoint = ['do', 'cause', 'effect', 'affect', 'impact', 'influence', 'change', 'modify', 'transform', 'alter', 'shift', 'adjust', 'adapt', 'convert', 'translate'];
|
||||
|
||||
// Generate routes
|
||||
for ($i = 0; $i < 1000; $i++) {
|
||||
$routes[] = makeRoute();
|
||||
// write the routes array to a file
|
||||
file_put_contents('big.txt', implode("\n", $routes));
|
||||
}
|
||||
|
||||
function makeRoute(): string
|
||||
{
|
||||
global $methods, $apis, $params, $endpoint, $midpoint;
|
||||
|
||||
$method = $methods[array_rand($methods)];
|
||||
$api = $apis[array_rand($apis)];
|
||||
$route = "/$api";
|
||||
$length = rand(1, 8);
|
||||
$options = ['params', 'endpoint', 'midpoint'];
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$option = $options[array_rand($options)];
|
||||
switch ($option) {
|
||||
case 'params':
|
||||
$param = $params[array_rand($params)];
|
||||
$route .= "/:$param";
|
||||
break;
|
||||
case 'endpoint':
|
||||
$end = $endpoint[array_rand($endpoint)];
|
||||
$route .= "/$end";
|
||||
break;
|
||||
case 'midpoint':
|
||||
$mid = $midpoint[array_rand($midpoint)];
|
||||
$route .= "/$mid";
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $method . ' ' . $route;
|
||||
}
|
79
tests/segment.php
Normal file
79
tests/segment.php
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
This test file puts the SegmentRouter to the test by running a million lookups on a handful
|
||||
of routes. The routes are read from two files, blog.txt and github.txt, and added to two separate
|
||||
routers. The lookups are then run in two separate loops, one for each router.
|
||||
Each lookup is timed and the memory usage is also printed out at regular intervals.
|
||||
The requests are randomly picked from the array of routes.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../SegmentRouter.php';
|
||||
require_once 'tools.php';
|
||||
|
||||
// Blog router
|
||||
$b = new SegmentRouter();
|
||||
$blog = readAndAddRoutes('blog.txt', $b);
|
||||
|
||||
// Github router
|
||||
$g = new SegmentRouter();
|
||||
$github = readAndAddRoutes('github.txt', $g);
|
||||
|
||||
// Big router
|
||||
$big = new SegmentRouter();
|
||||
$bigRoutes = readAndAddRoutes('big.txt', $big);
|
||||
|
||||
echoTitle("Starting github lookups");
|
||||
runIterations(100000, $b, $blog);
|
||||
runIterations(1000000, $b, $blog);
|
||||
|
||||
echoTitle("Starting github lookups");
|
||||
runIterations(10000, $g, $github);
|
||||
runIterations(100000, $g, $github);
|
||||
runIterations(1000000, $g, $github);
|
||||
|
||||
echoTitle("Starting big lookups");
|
||||
runIterations(10000, $big, $bigRoutes);
|
||||
runIterations(100000, $big, $bigRoutes);
|
||||
runIterations(1000000, $big, $bigRoutes);
|
||||
|
||||
echoTitle("Testing parameters");
|
||||
|
||||
$routes = [
|
||||
['GET', '/blog/:id', function($id) {
|
||||
echo $id."\n";
|
||||
}],
|
||||
['GET', '/blog/:id/:slug', function($id, $slug) {
|
||||
echo $id . ' - ' . $slug."\n";
|
||||
}],
|
||||
['GET', '/blog/:id/:slug/:page', function($id, $slug, $page) {
|
||||
echo $id . ' - ' . $slug . ' - ' . $page."\n";
|
||||
}],
|
||||
['GET', '/blog/:id/:slug/:page/:extra', function($id, $slug, $page, $extra) {
|
||||
echo $id . ' - ' . $slug . ' - ' . $page . ' - ' . $extra."\n";
|
||||
}],
|
||||
];
|
||||
|
||||
$r = new SegmentRouter();
|
||||
foreach ($routes as $route) {
|
||||
[$method, $path, $handler] = $route;
|
||||
$r->add($method, $path, $handler);
|
||||
}
|
||||
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
[$method, $uri] = $routes[array_rand($routes)];
|
||||
|
||||
// Generate some random parameters
|
||||
$uri = str_replace(':id', rand(1, 100), $uri);
|
||||
$uri = str_replace(':slug', 'slug-' . rand(1, 100), $uri);
|
||||
$uri = str_replace(':page', rand(1, 100), $uri);
|
||||
$uri = str_replace(':extra', 'extra-' . rand(1, 100), $uri);
|
||||
|
||||
$res = $r->lookup($method, $uri);
|
||||
if ($res !== 200) {
|
||||
echo "Failed to handle request for $uri - $res\n";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
exit(0);
|
117
tests/tools.php
Normal file
117
tests/tools.php
Normal file
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
// A simple class to return a string wrapped in ANSI color codes for terminal output
|
||||
class Color {
|
||||
const RESET = "\033[0m";
|
||||
const BOLD = "\033[1m";
|
||||
const UNDERLINE = "\033[4m";
|
||||
const INVERSE = "\033[7m";
|
||||
const BLACK = "\033[30m";
|
||||
const RED = "\033[31m";
|
||||
const GREEN = "\033[32m";
|
||||
const YELLOW = "\033[33m";
|
||||
const BLUE = "\033[34m";
|
||||
const MAGENTA = "\033[35m";
|
||||
const CYAN = "\033[36m";
|
||||
const WHITE = "\033[37m";
|
||||
|
||||
private static function format(string $color, string $string): string {
|
||||
return $color . $string . self::RESET;
|
||||
}
|
||||
|
||||
public static function bold(string $string): string {
|
||||
return self::format(self::BOLD, $string);
|
||||
}
|
||||
|
||||
public static function underline(string $string): string {
|
||||
return self::format(self::UNDERLINE, $string);
|
||||
}
|
||||
|
||||
public static function inverse(string $string): string {
|
||||
return self::format(self::INVERSE, $string);
|
||||
}
|
||||
|
||||
public static function black(string $string): string {
|
||||
return self::format(self::BLACK, $string);
|
||||
}
|
||||
|
||||
public static function red(string $string): string {
|
||||
return self::format(self::RED, $string);
|
||||
}
|
||||
|
||||
public static function green(string $string): string {
|
||||
return self::format(self::GREEN, $string);
|
||||
}
|
||||
|
||||
public static function yellow(string $string): string {
|
||||
return self::format(self::YELLOW, $string);
|
||||
}
|
||||
|
||||
public static function blue(string $string): string {
|
||||
return self::format(self::BLUE, $string);
|
||||
}
|
||||
|
||||
public static function magenta(string $string): string {
|
||||
return self::format(self::MAGENTA, $string);
|
||||
}
|
||||
|
||||
public static function cyan(string $string): string {
|
||||
return self::format(self::CYAN, $string);
|
||||
}
|
||||
|
||||
public static function white(string $string): string {
|
||||
return self::format(self::WHITE, $string);
|
||||
}
|
||||
}
|
||||
|
||||
function echoMemoryAndTime(int $i, float $start) {
|
||||
echo "(".Color::green("$i lookups").") M: " .
|
||||
Color::blue(round(memory_get_usage() / 1024, 1) . " kb") .
|
||||
" - T: ". Color::blue(number_format(microtime(true) - $start, 10) ." s") .
|
||||
"\n";
|
||||
}
|
||||
|
||||
function echoTitle(string $title) {
|
||||
echo "\n";
|
||||
echo Color::bold(Color::black("===============================================================")) . "\n";
|
||||
echo "\n";
|
||||
echo Color::bold(Color::blue($title))."\n";
|
||||
echo "\n";
|
||||
echo Color::bold(Color::black("===============================================================")) . "\n";
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
function readAndAddRoutes(string $file, &$r): array
|
||||
{
|
||||
$array = [];
|
||||
$routes = file($file);
|
||||
foreach ($routes as $route) {
|
||||
[$method, $path] = explode(' ', $route);
|
||||
$path = trim($path);
|
||||
$array[] = [$method, $path];
|
||||
$r->add($method, $path, function() {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
function runIterations(int $iterations, $r, array $routes) {
|
||||
echo "Running $iterations iterations\n";
|
||||
$start = microtime(true);
|
||||
$interval = $iterations / 10;
|
||||
for ($i = 0; $i < $iterations; $i++) {
|
||||
// pick a random route from the array
|
||||
[$method, $uri] = $routes[array_rand($routes)];
|
||||
$res = $r->lookup($method, $uri);
|
||||
if ($res !== 200) {
|
||||
echo Color::red("Failed to handle request for $uri - $res\n");
|
||||
exit(1);
|
||||
}
|
||||
if ($i !== 0 && $i % ($interval) === 0) echoMemoryAndTime($i, $start);
|
||||
}
|
||||
echo "Time: " . Color::cyan(number_format(microtime(true) - $start, 10) . " s\n");
|
||||
// echo the average time per request
|
||||
echo "Avg/lookup: " . Color::yellow(number_format((microtime(true) - $start) / $iterations, 10) . " s\n");
|
||||
echo "\n";
|
||||
}
|
60
tests/trie.php
Normal file
60
tests/trie.php
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
This test file puts the PATRICIA trie to the test by running a million lookups on a handful
|
||||
of routes. The routes are read from two files, blog.txt and github.txt, and added to two separate
|
||||
routers and two separate arrays. The lookups are then run in two separate loops, one for each router.
|
||||
Each lookup is timed and the memory usage is also printed out at regular intervals.
|
||||
The requests are randomly picked from the array of routes.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../TrieRouter.php';
|
||||
require_once 'tools.php';
|
||||
|
||||
// Blog router
|
||||
$b = new TrieRouter();
|
||||
$blog = readAndAddRoutes('blog.txt', $b);
|
||||
|
||||
// Github router
|
||||
$g = new TrieRouter();
|
||||
$github = readAndAddRoutes('github.txt', $g);
|
||||
|
||||
// Big router
|
||||
$big = new TrieRouter();
|
||||
$bigRoutes = readAndAddRoutes('big.txt', $big);
|
||||
|
||||
function runIterations(int $iterations, TrieRouter $r, array $routes) {
|
||||
echo "Running $iterations iterations\n";
|
||||
$start = microtime(true);
|
||||
$interval = $iterations / 10;
|
||||
for ($i = 0; $i < $iterations; $i++) {
|
||||
// pick a random route from the array
|
||||
[$method, $uri] = $routes[array_rand($routes)];
|
||||
$res = $r->handleRequest($method, $uri);
|
||||
if ($res !== true) {
|
||||
echo "Failed to handle request for $uri\n";
|
||||
exit(1);
|
||||
}
|
||||
if ($i !== 0 && $i % ($interval) === 0) echoMemoryAndTime($i, $start);
|
||||
}
|
||||
echo "Time: " . Color::cyan(number_format(microtime(true) - $start, 10) . " s\n");
|
||||
// echo the average time per request
|
||||
echo "Avg/lookup: " . Color::yellow(number_format((microtime(true) - $start) / $iterations, 10) . " s\n");
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
echoTitle("Starting blog lookups");
|
||||
runIterations(100000, $b, $blog);
|
||||
runIterations(1000000, $b, $blog);
|
||||
|
||||
echoTitle("Starting github lookups");
|
||||
runIterations(10000, $g, $github);
|
||||
runIterations(100000, $g, $github);
|
||||
runIterations(1000000, $g, $github);
|
||||
|
||||
echoTitle("Starting big lookups");
|
||||
runIterations(10000, $big, $bigRoutes);
|
||||
runIterations(100000, $big, $bigRoutes);
|
||||
runIterations(1000000, $big, $bigRoutes);
|
||||
|
||||
exit(0);
|
Loading…
Reference in New Issue
Block a user