diff --git a/database/live.db b/database/live.db index cced5b2..dcc54c5 100644 Binary files a/database/live.db and b/database/live.db differ diff --git a/public/index.php b/public/index.php index 9a2be78..86cc759 100644 --- a/public/index.php +++ b/public/index.php @@ -38,7 +38,10 @@ router_post($r, '/character/delete', 'char_controller_delete_post'); Router */ // [code, handler, params] +stopwatch_start('router'); $l = router_lookup($r, $_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']); +stopwatch_stop('router'); + if ($l['code'] !== 200) router_error($l['code']); $l['handler'](...$l['params'] ?? []); @@ -46,3 +49,8 @@ $l['handler'](...$l['params'] ?? []); Cleanup */ clear_flashes(); + +/* + Stopwatch +*/ +if (env('debug')) echo c_debug_stopwatch(); diff --git a/src/auth.php b/src/auth.php index fd36f06..bf7df2c 100644 --- a/src/auth.php +++ b/src/auth.php @@ -76,3 +76,12 @@ function must_have_character() change_user_character($char['id']); } } + +/** + * The user must be authenticated and have a character. + */ +function auth_only_and_must_have_character() +{ + auth_only(); + must_have_character(); +} diff --git a/src/bootstrap.php b/src/bootstrap.php index 38414b3..5f32871 100644 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -23,6 +23,9 @@ require_once SRC . '/models/char.php'; require_once SRC . '/controllers/char.php'; require_once SRC . '/controllers/auth.php'; +// Track the start time of the request +define('START_TIME', microtime(true)); + /* Load env, set error reporting, etc. */ @@ -34,14 +37,19 @@ if (env('debug') === 'true') { error_reporting(E_ALL); } +stopwatch_start('bootstrap'); // Start the bootstrap stopwatch + // Generate a new CSRF token. (if one doesn't exist, that is) csrf(); -// Have a global counter for queries +// Have global counters for queries $GLOBALS['queries'] = 0; +$GLOBALS['query_time'] = 0; // Set the default page layout page_layout('basic'); // Run auth_check to see if we're logged in, since it populates the user data in SESSION auth_check(); + +stopwatch_stop('bootstrap'); // Stop the bootstrap stopwatch diff --git a/src/components.php b/src/components.php index df713ae..41e64e8 100644 --- a/src/components.php +++ b/src/components.php @@ -49,7 +49,7 @@ function c_debug_query_log() /** * Render the character select radio buttons. */ -function c_char_select_box($id, array $char) +function c_char_select_box($id, $char) { return render('components/char_select_box', ['id' => $id, 'char' => $char]); } @@ -88,3 +88,11 @@ function c_form_field($type, $name, $placeholder, $required = false, $autocomple $html .= $errors !== false && !empty($errors[$name]) ? ' class="form control error"' : ' class="form control"'; return $html . " autocomplete=\"$autocomplete\">"; } + +/** + * Render the stopwatch debug component. + */ +function c_debug_stopwatch() +{ + return render('components/debug_stopwatch'); +} diff --git a/src/controllers/char.php b/src/controllers/char.php index c636e1a..b9e5e2e 100644 --- a/src/controllers/char.php +++ b/src/controllers/char.php @@ -5,7 +5,7 @@ */ function char_controller_list_get() { - auth_only(); must_have_character(); + auth_only_and_must_have_character(); $GLOBALS['active_nav_tab'] = 'chars'; echo page('chars/list', ['chars' => char_list(user('id'))]); @@ -16,7 +16,7 @@ function char_controller_list_get() */ function char_controller_list_post() { - auth_only(); must_have_character(); csrf_ensure(); + auth_only_and_must_have_character(); csrf_ensure(); $GLOBALS['active_nav_tab'] = 'chars'; @@ -68,7 +68,7 @@ function char_controller_list_post() */ function char_controller_delete_post() { - auth_only(); must_have_character(); csrf_ensure(); + auth_only_and_must_have_character(); csrf_ensure(); $char_id = (int) ($_POST['char_id'] ?? 0); diff --git a/src/database.php b/src/database.php index d5b5a34..6de165a 100644 --- a/src/database.php +++ b/src/database.php @@ -3,7 +3,7 @@ /** * Open a connection to a database. */ -function db_open($path): SQLite3 +function db_open($path) { $db = new SQLite3($path); @@ -20,7 +20,7 @@ function db_open($path): SQLite3 /** * Return a connection to the auth database. */ -function db_auth(): SQLite3 +function db_auth() { return $GLOBALS['db_auth'] ??= db_open(__DIR__ . '/../database/auth.db'); } @@ -28,7 +28,7 @@ function db_auth(): SQLite3 /** * Return a connection to the live database. */ -function db_live(): SQLite3 +function db_live() { return $GLOBALS['db_live'] ??= db_open(__DIR__ . '/../database/live.db'); } @@ -37,7 +37,7 @@ function db_live(): SQLite3 /** * Return a connection to the fights database. */ -function db_fights(): SQLite3 +function db_fights() { return $GLOBALS['db_fights'] ??= db_open(__DIR__ . '/../database/fights.db'); } @@ -46,7 +46,7 @@ function db_fights(): SQLite3 /** * Return a connection to the blueprints database. */ -function db_blueprints(): SQLite3 +function db_blueprints() { return $GLOBALS['db_blueprints'] ??= db_open(__DIR__ . '/../database/blueprints.db'); } @@ -55,28 +55,32 @@ function db_blueprints(): SQLite3 * Take a SQLite3 database connection, a query string, and an array of parameters. Prepare the query and * bind the parameters with proper type casting. Then execute the query and return the result. */ -function db_query(SQLite3 $db, $query, array $params = []) +function db_query($db, $query, $params = []) { $stmt = $db->prepare($query); if (!empty($params)) foreach ($params as $key => $value) $stmt->bindValue($key, $value, getSQLiteType($value)); - db_log($query); - return $stmt->execute(); + $start = microtime(true); + $r = $stmt->execute(); + db_log($query, microtime(true) - $start); + return $r; } /** * Take a SQLite3 database connection and a query string. Execute the query and return the result. */ -function db_exec(SQLite3 $db, $query) +function db_exec($db, $query) { - db_log($query); - return $db->exec($query); + $start = microtime(true); + $r = $db->exec($query); + db_log($query, microtime(true) - $start); + return $r; } /** * Take a SQLite3 database connection, a column name, and a value. Execute a COUNT query to see if the value * exists in the column. Return true if the value exists, false otherwise. */ -function db_exists(SQLite3 $db, $table, $column, $value, $caseInsensitive = true) +function db_exists($db, $table, $column, $value, $caseInsensitive = true) { if ($caseInsensitive) { $query = "SELECT 1 FROM $table WHERE $column = :v COLLATE NOCASE LIMIT 1"; @@ -104,8 +108,9 @@ function getSQLiteType($value): int /** * Log the given query string to the db debug log. */ -function db_log($query) +function db_log($query, $timeTaken = 0) { $GLOBALS['queries']++; - if (env('debug', false)) $GLOBALS['query_log'][] = $query; + $GLOBALS['query_time'] += $timeTaken; + if (env('debug', false)) $GLOBALS['query_log'][] = [$query, $timeTaken]; } diff --git a/src/env.php b/src/env.php index d09dcdd..3fc1ab1 100644 --- a/src/env.php +++ b/src/env.php @@ -36,5 +36,12 @@ function env_load($filePath) */ function env($key, $default = null) { - return $_ENV[$key] ?? $_SERVER[$key] ?? (getenv($key) ?: $default); + $v = $_ENV[$key] ?? $_SERVER[$key] ?? (getenv($key) ?: $default); + return match(true) { + $v === 'true' => true, + $v === 'false' => false, + is_numeric($v) => (int) $v, + is_float($v) => (float) $v, + default => $v + }; } diff --git a/src/helpers.php b/src/helpers.php index 6b31a1d..06b3fa7 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -171,7 +171,7 @@ function percent($num, $denom, $precision = 4): int * the data is up to date with every request without having to query the database every use within, for example, a * template. Will return false if the field does not exist, or the entire wallet array if no field is specified. */ -function wallet($field = ''): array|int +function wallet($field = '') { if (empty($GLOBALS['wallet'])) { $GLOBALS['wallet'] = db_query( @@ -188,9 +188,38 @@ function wallet($field = ''): array|int /** * Format an array of strings to a ul element. */ -function array_to_ul(array $array) +function array_to_ul($array) { $html = ''; foreach ($array as $item) $html .= "
= $GLOBALS['queries'] ?> queries were executed.
- $query"; ?> + ({$time}s) {$query[0]}"; + } + ?>Page execution took = number_format((microtime(true) - START_TIME), 10) ?> seconds.
+Bootstrap: = stopwatch_get('bootstrap') ?> seconds
+Router: = stopwatch_get('router') ?> seconds
+