Fixes: 1. media.ts: wrap placeholder generation in try-catch 2. toolbar.ts: check r.ok, display error message in popover
174 lines
6.3 KiB
JavaScript
174 lines
6.3 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* Analyse the per-route query dumps and classify each query.
|
||
*/
|
||
import { readFileSync, readdirSync, writeFileSync } from "node:fs";
|
||
import { dirname, resolve } from "node:path";
|
||
import { fileURLToPath } from "node:url";
|
||
|
||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||
|
||
function classify(sql, params) {
|
||
const s = sql.replace(/\s+/g, " ").trim();
|
||
// Migrations / system
|
||
if (/pragma_table_info/i.test(s)) return "pragma_table_info";
|
||
if (/sqlite_master/i.test(s)) return "sqlite_master";
|
||
if (/PRAGMA/i.test(s)) return "pragma";
|
||
if (/from "kysely_migration"/i.test(s)) return "migrations_check";
|
||
if (/from "_emdash_migrations_lock"/i.test(s)) return "migrations_lock";
|
||
if (/from "_emdash_migrations"/i.test(s)) return "migrations_check";
|
||
if (/from "_emdash_collections"/i.test(s)) return "schema_collections";
|
||
if (/from "_emdash_fields"/i.test(s)) return "schema_fields";
|
||
if (/from "_emdash_setup_state"/i.test(s)) return "setup_check";
|
||
if (/from "_plugin_state"/i.test(s)) return "plugin_state";
|
||
if (/from "_emdash_cron_tasks"/i.test(s) || /_emdash_cron_tasks SET/i.test(s))
|
||
return "cron_recovery";
|
||
if (/_emdash_404_log/i.test(s)) return "404_log_migration";
|
||
if (/alter table/i.test(s)) return "ddl_alter";
|
||
if (/create table/i.test(s)) return "ddl_create";
|
||
if (/create.*index/i.test(s)) return "ddl_index";
|
||
if (/drop index/i.test(s)) return "ddl_drop_index";
|
||
if (/insert into "_emdash_migrations"/i.test(s)) return "migrations_record";
|
||
if (/delete from "options"/i.test(s)) return "options_delete";
|
||
if (/SELECT name FROM sqlite_master/i.test(s)) return "fts_table_check";
|
||
// Auth
|
||
if (/from "_emdash_sessions"/i.test(s)) return "auth_session";
|
||
if (/from "_emdash_users"/i.test(s)) return "auth_user_lookup";
|
||
if (/from "_emdash_passkeys"/i.test(s)) return "auth_passkey";
|
||
// Settings/options
|
||
if (/from "options"/i.test(s) && /LIKE/i.test(s)) {
|
||
const p0 = params?.[0];
|
||
if (typeof p0 === "string") return `options_prefix:${p0}`;
|
||
return "options_prefix";
|
||
}
|
||
if (/from "options"/i.test(s) && /"name" in/i.test(s)) {
|
||
return "options_in";
|
||
}
|
||
if (/from "options"/i.test(s) && /"name" = \?/i.test(s)) {
|
||
const p0 = params?.[0];
|
||
if (typeof p0 === "string") return `option:${p0}`;
|
||
return "option:single";
|
||
}
|
||
if (/from "options"/i.test(s)) return "option:other";
|
||
// Menus / widgets
|
||
if (/from "_emdash_menus"/i.test(s)) return "menu_lookup";
|
||
if (/from "_emdash_menu_items"/i.test(s)) return "menu_items";
|
||
if (/from "_emdash_widget_areas"/i.test(s)) {
|
||
const p0 = params?.[0];
|
||
return `widget_area:${p0 ?? ""}`;
|
||
}
|
||
if (/from "_emdash_widgets"/i.test(s)) return "widget";
|
||
// Bylines
|
||
if (/from "_emdash_content_bylines"/i.test(s)) return "byline_hydration";
|
||
if (/from "_emdash_bylines"/i.test(s)) return "byline_lookup";
|
||
// Taxonomies
|
||
if (/from "_emdash_taxonomy_defs"/i.test(s)) return "taxonomy_defs";
|
||
if (/from "content_taxonomies"/i.test(s) && /count\(/i.test(s)) return "taxonomy_counts";
|
||
if (/from "content_taxonomies"/i.test(s)) return "taxonomy_for_entries";
|
||
if (/from "taxonomies"/i.test(s)) return "taxonomy_terms";
|
||
// Author lookup (id, author_id)
|
||
if (/SELECT id, author_id FROM "ec_/i.test(s)) return "author_id_lookup";
|
||
// Content tables
|
||
const ecMatch = s.match(/from "ec_([a-z_]+)"/i) || s.match(/FROM "ec_([a-z_]+)"/);
|
||
if (ecMatch) {
|
||
const coll = ecMatch[1];
|
||
// detail vs list
|
||
if (/slug = \?/i.test(s) && /id = \?/i.test(s)) return `entry_by_slug:${coll}`;
|
||
if (/where "id" = \?/i.test(s)) return `entry_by_id:${coll}`;
|
||
if (/LIMIT \?/i.test(s)) return `collection_list:${coll}`;
|
||
if (/ORDER BY/i.test(s)) return `collection_list:${coll}`;
|
||
return `collection_other:${coll}`;
|
||
}
|
||
// Media
|
||
if (/from "_emdash_media"/i.test(s)) return "media_lookup";
|
||
// Plugins / pages dispatch
|
||
if (/from "_emdash_plugin/i.test(s)) return "plugin_lookup";
|
||
// SEO redirects
|
||
if (/from "_emdash_redirects"/i.test(s)) return "redirects";
|
||
// Comments
|
||
if (/from "_emdash_comments"/i.test(s)) return "comments";
|
||
// Default
|
||
return "other";
|
||
}
|
||
|
||
const targetArg = process.argv[2] || "sqlite";
|
||
const dir = resolve(__dirname, targetArg);
|
||
const files = readdirSync(dir).filter((f) => f.endsWith(".json") && f !== "_all.json");
|
||
|
||
// per-route classification table
|
||
const tables = {}; // { routePhase: { className: count } }
|
||
const allByClass = {}; // { className: count }
|
||
const totalDuration = {}; // { className: ms }
|
||
|
||
const routeOrder = [
|
||
"root",
|
||
"posts",
|
||
"posts_building_for_the_long_term",
|
||
"pages_about",
|
||
"category_development",
|
||
"tag_webdev",
|
||
"rss_xml",
|
||
"search",
|
||
];
|
||
|
||
const phases = ["cold", "warm"];
|
||
|
||
for (const f of files) {
|
||
const path = resolve(dir, f);
|
||
const events = JSON.parse(readFileSync(path, "utf8"));
|
||
const key = f.replace(/\.json$/, "");
|
||
tables[key] = {};
|
||
for (const e of events) {
|
||
const cls = classify(e.sql, e.params);
|
||
tables[key][cls] = (tables[key][cls] || 0) + 1;
|
||
allByClass[cls] = (allByClass[cls] || 0) + 1;
|
||
totalDuration[cls] = (totalDuration[cls] || 0) + (e.durationMs || 0);
|
||
}
|
||
}
|
||
|
||
// print table: rows = classes, cols = route.phase
|
||
const headerRoutes = [];
|
||
for (const r of routeOrder) for (const p of phases) headerRoutes.push(`${r}.${p}`);
|
||
|
||
const allClasses = Object.keys(allByClass).toSorted(
|
||
(a, b) => (allByClass[b] || 0) - (allByClass[a] || 0),
|
||
);
|
||
|
||
let out = `# Query classification (${targetArg})\n\n`;
|
||
out += `Total events: ${Object.values(allByClass).reduce((a, b) => a + b, 0)}\n\n`;
|
||
|
||
out += `## Top classes by total count\n\n`;
|
||
out += `| class | count | total_ms |\n|---|---:|---:|\n`;
|
||
for (const c of allClasses.slice(0, 20)) {
|
||
out += `| ${c} | ${allByClass[c]} | ${totalDuration[c].toFixed(2)} |\n`;
|
||
}
|
||
out += "\n";
|
||
|
||
out += `## Per-route × phase classification\n\n`;
|
||
out += `| class |`;
|
||
for (const h of headerRoutes) out += ` ${h} |`;
|
||
out += ` total |\n|---|`;
|
||
for (const _ of headerRoutes) out += "---:|";
|
||
out += "---:|\n";
|
||
for (const c of allClasses) {
|
||
out += `| ${c} |`;
|
||
let total = 0;
|
||
for (const h of headerRoutes) {
|
||
const v = tables[h]?.[c] || 0;
|
||
total += v;
|
||
out += ` ${v || ""} |`;
|
||
}
|
||
out += ` ${total} |\n`;
|
||
}
|
||
out += "\n";
|
||
|
||
out += `## Per-route totals\n\n`;
|
||
out += `| route.phase | count |\n|---|---:|\n`;
|
||
for (const h of headerRoutes) {
|
||
const total = Object.values(tables[h] || {}).reduce((a, b) => a + b, 0);
|
||
out += `| ${h} | ${total} |\n`;
|
||
}
|
||
|
||
writeFileSync(resolve(__dirname, `classification.${targetArg}.md`), out);
|
||
process.stdout.write(out);
|