From 59ddcb64a6935a2aa3457e5b2585d424f11ca2eb Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 13 Aug 2023 00:34:03 +0200 Subject: [PATCH] feat: performance tracker --- fresh.gen.ts | 132 +++++++++++++++-------------- lib/cache/performance.ts | 86 +++++++++++++++++++ lib/search.ts | 2 - routes/_app.tsx | 1 + routes/_middleware.ts | 18 ++-- routes/admin/performance/api.ts | 9 ++ routes/admin/performance/index.tsx | 93 ++++++++++++++++++++ 7 files changed, 269 insertions(+), 72 deletions(-) create mode 100644 lib/cache/performance.ts create mode 100644 routes/admin/performance/api.ts create mode 100644 routes/admin/performance/index.tsx diff --git a/fresh.gen.ts b/fresh.gen.ts index a138dcb..de9850e 100644 --- a/fresh.gen.ts +++ b/fresh.gen.ts @@ -5,38 +5,40 @@ import * as $0 from "./routes/_404.tsx"; import * as $1 from "./routes/_app.tsx"; import * as $2 from "./routes/_middleware.ts"; -import * as $3 from "./routes/api/articles/[name].ts"; -import * as $4 from "./routes/api/articles/create/index.ts"; -import * as $5 from "./routes/api/articles/index.ts"; -import * as $6 from "./routes/api/auth/callback.ts"; -import * as $7 from "./routes/api/auth/login.ts"; -import * as $8 from "./routes/api/auth/logout.ts"; -import * as $9 from "./routes/api/cache/index.ts"; -import * as $10 from "./routes/api/images/index.ts"; -import * as $11 from "./routes/api/index.ts"; -import * as $12 from "./routes/api/movies/[name].ts"; -import * as $13 from "./routes/api/movies/enhance/[name].ts"; -import * as $14 from "./routes/api/movies/index.ts"; -import * as $15 from "./routes/api/query/index.ts"; -import * as $16 from "./routes/api/query/sync.ts"; -import * as $17 from "./routes/api/recipes/[name].ts"; -import * as $18 from "./routes/api/recipes/index.ts"; -import * as $19 from "./routes/api/resources.ts"; -import * as $20 from "./routes/api/series/[name].ts"; -import * as $21 from "./routes/api/series/enhance/[name].ts"; -import * as $22 from "./routes/api/series/index.ts"; -import * as $23 from "./routes/api/tmdb/[id].ts"; -import * as $24 from "./routes/api/tmdb/credits/[id].ts"; -import * as $25 from "./routes/api/tmdb/query.ts"; -import * as $26 from "./routes/articles/[name].tsx"; -import * as $27 from "./routes/articles/index.tsx"; -import * as $28 from "./routes/index.tsx"; -import * as $29 from "./routes/movies/[name].tsx"; -import * as $30 from "./routes/movies/index.tsx"; -import * as $31 from "./routes/recipes/[name].tsx"; -import * as $32 from "./routes/recipes/index.tsx"; -import * as $33 from "./routes/series/[name].tsx"; -import * as $34 from "./routes/series/index.tsx"; +import * as $3 from "./routes/admin/performance/api.ts"; +import * as $4 from "./routes/admin/performance/index.tsx"; +import * as $5 from "./routes/api/articles/[name].ts"; +import * as $6 from "./routes/api/articles/create/index.ts"; +import * as $7 from "./routes/api/articles/index.ts"; +import * as $8 from "./routes/api/auth/callback.ts"; +import * as $9 from "./routes/api/auth/login.ts"; +import * as $10 from "./routes/api/auth/logout.ts"; +import * as $11 from "./routes/api/cache/index.ts"; +import * as $12 from "./routes/api/images/index.ts"; +import * as $13 from "./routes/api/index.ts"; +import * as $14 from "./routes/api/movies/[name].ts"; +import * as $15 from "./routes/api/movies/enhance/[name].ts"; +import * as $16 from "./routes/api/movies/index.ts"; +import * as $17 from "./routes/api/query/index.ts"; +import * as $18 from "./routes/api/query/sync.ts"; +import * as $19 from "./routes/api/recipes/[name].ts"; +import * as $20 from "./routes/api/recipes/index.ts"; +import * as $21 from "./routes/api/resources.ts"; +import * as $22 from "./routes/api/series/[name].ts"; +import * as $23 from "./routes/api/series/enhance/[name].ts"; +import * as $24 from "./routes/api/series/index.ts"; +import * as $25 from "./routes/api/tmdb/[id].ts"; +import * as $26 from "./routes/api/tmdb/credits/[id].ts"; +import * as $27 from "./routes/api/tmdb/query.ts"; +import * as $28 from "./routes/articles/[name].tsx"; +import * as $29 from "./routes/articles/index.tsx"; +import * as $30 from "./routes/index.tsx"; +import * as $31 from "./routes/movies/[name].tsx"; +import * as $32 from "./routes/movies/index.tsx"; +import * as $33 from "./routes/recipes/[name].tsx"; +import * as $34 from "./routes/recipes/index.tsx"; +import * as $35 from "./routes/series/[name].tsx"; +import * as $36 from "./routes/series/index.tsx"; import * as $$0 from "./islands/Counter.tsx"; import * as $$1 from "./islands/IngredientsList.tsx"; import * as $$2 from "./islands/KMenu.tsx"; @@ -54,38 +56,40 @@ const manifest = { "./routes/_404.tsx": $0, "./routes/_app.tsx": $1, "./routes/_middleware.ts": $2, - "./routes/api/articles/[name].ts": $3, - "./routes/api/articles/create/index.ts": $4, - "./routes/api/articles/index.ts": $5, - "./routes/api/auth/callback.ts": $6, - "./routes/api/auth/login.ts": $7, - "./routes/api/auth/logout.ts": $8, - "./routes/api/cache/index.ts": $9, - "./routes/api/images/index.ts": $10, - "./routes/api/index.ts": $11, - "./routes/api/movies/[name].ts": $12, - "./routes/api/movies/enhance/[name].ts": $13, - "./routes/api/movies/index.ts": $14, - "./routes/api/query/index.ts": $15, - "./routes/api/query/sync.ts": $16, - "./routes/api/recipes/[name].ts": $17, - "./routes/api/recipes/index.ts": $18, - "./routes/api/resources.ts": $19, - "./routes/api/series/[name].ts": $20, - "./routes/api/series/enhance/[name].ts": $21, - "./routes/api/series/index.ts": $22, - "./routes/api/tmdb/[id].ts": $23, - "./routes/api/tmdb/credits/[id].ts": $24, - "./routes/api/tmdb/query.ts": $25, - "./routes/articles/[name].tsx": $26, - "./routes/articles/index.tsx": $27, - "./routes/index.tsx": $28, - "./routes/movies/[name].tsx": $29, - "./routes/movies/index.tsx": $30, - "./routes/recipes/[name].tsx": $31, - "./routes/recipes/index.tsx": $32, - "./routes/series/[name].tsx": $33, - "./routes/series/index.tsx": $34, + "./routes/admin/performance/api.ts": $3, + "./routes/admin/performance/index.tsx": $4, + "./routes/api/articles/[name].ts": $5, + "./routes/api/articles/create/index.ts": $6, + "./routes/api/articles/index.ts": $7, + "./routes/api/auth/callback.ts": $8, + "./routes/api/auth/login.ts": $9, + "./routes/api/auth/logout.ts": $10, + "./routes/api/cache/index.ts": $11, + "./routes/api/images/index.ts": $12, + "./routes/api/index.ts": $13, + "./routes/api/movies/[name].ts": $14, + "./routes/api/movies/enhance/[name].ts": $15, + "./routes/api/movies/index.ts": $16, + "./routes/api/query/index.ts": $17, + "./routes/api/query/sync.ts": $18, + "./routes/api/recipes/[name].ts": $19, + "./routes/api/recipes/index.ts": $20, + "./routes/api/resources.ts": $21, + "./routes/api/series/[name].ts": $22, + "./routes/api/series/enhance/[name].ts": $23, + "./routes/api/series/index.ts": $24, + "./routes/api/tmdb/[id].ts": $25, + "./routes/api/tmdb/credits/[id].ts": $26, + "./routes/api/tmdb/query.ts": $27, + "./routes/articles/[name].tsx": $28, + "./routes/articles/index.tsx": $29, + "./routes/index.tsx": $30, + "./routes/movies/[name].tsx": $31, + "./routes/movies/index.tsx": $32, + "./routes/recipes/[name].tsx": $33, + "./routes/recipes/index.tsx": $34, + "./routes/series/[name].tsx": $35, + "./routes/series/index.tsx": $36, }, islands: { "./islands/Counter.tsx": $$0, diff --git a/lib/cache/performance.ts b/lib/cache/performance.ts new file mode 100644 index 0000000..93f5d36 --- /dev/null +++ b/lib/cache/performance.ts @@ -0,0 +1,86 @@ +import * as cache from "@lib/cache/cache.ts"; + +export type PerformancePoint = { + path: string; + search: string; + time: number; + date: Date; +}; + +export type PerformanceRes = { + max: number; + res: { + url: string; + data: readonly [number, number, number, number]; + }[]; +}; + +export const savePerformance = (url: string, milliseconds: number) => { + const d = new Date(); + const year = d.getFullYear(); + const month = d.getMonth().toString().padStart(2, "0"); + const day = d.getDay().toString().padStart(2, "0"); + const hour = d.getHours().toString().padStart(2, "0"); + const minute = d.getMinutes().toString().padStart(2, "0"); + const seconds = d.getSeconds().toString().padStart(2, "0"); + + const u = new URL(url); + if (u.pathname.includes("_frsh/")) return; + u.searchParams.delete("__frsh_c"); + + cache.set( + `performance:${year}:${month}:${day}:${hour}:${minute}:${seconds}`, + JSON.stringify({ + path: decodeURIComponent(u.pathname), + search: u.search, + time: Math.floor(milliseconds * 1000), + }), + ); +}; + +export async function getPerformances(): Promise { + const d = new Date(); + const year = d.getFullYear(); + const month = d.getMonth().toString().padStart(2, "0"); + const day = d.getDay().toString().padStart(2, "0"); + + const keys = await cache.keys( + `performance:${year}:${month}:${day}:*`, + ); + + const performances = await Promise.all( + keys.map(async (key) => + JSON.parse(await cache.get(key)) as PerformancePoint + ), + ); + + let maximum = 0; + + const res = new Map(); + for (const p of performances) { + const id = p.path; + const timings = res.get(id) || []; + if (p.time > maximum) maximum = p.time; + timings.push(p.time); + res.set(id, timings); + } + + return { + max: maximum, + res: [...res.entries()].map(([url, timings]) => { + let total = 0; + let min = Infinity; + let max = -Infinity; + for (const t of timings) { + total += t; + if (t < min) min = t; + if (t > max) max = t; + } + + return { + url, + data: [timings.length, min, total / timings.length, max] as const, + }; + }).sort((a, b) => a.data[2] < b.data[2] ? 1 : -1), + }; +} diff --git a/lib/search.ts b/lib/search.ts index 59ce41c..ac9fea4 100644 --- a/lib/search.ts +++ b/lib/search.ts @@ -71,8 +71,6 @@ export async function searchResource( } } - console.log({ q, query_by, filter_by }); - return await typesenseClient.collections("resources") .documents().search({ q, diff --git a/routes/_app.tsx b/routes/_app.tsx index 98dde8f..c980732 100644 --- a/routes/_app.tsx +++ b/routes/_app.tsx @@ -36,6 +36,7 @@ export default function App({ Component }: AppProps) { :root { --background: rgb(43, 41, 48); --foreground: rgb(129, 129, 129); + --light: #2B2930; } .custom-grid { grid-template-columns: repeat(auto-fit, minmax(37vw, 1fr)) ; diff --git a/routes/_middleware.ts b/routes/_middleware.ts index ac35d1c..71d207c 100644 --- a/routes/_middleware.ts +++ b/routes/_middleware.ts @@ -2,17 +2,17 @@ import { MiddlewareHandlerContext } from "$fresh/server.ts"; import { DomainError } from "@lib/errors.ts"; import { getCookies } from "https://deno.land/std@0.197.0/http/cookie.ts"; -import { decode, verify } from "https://deno.land/x/djwt@v2.2/mod.ts"; -import { sessionDB, userDB } from "@lib/db.ts"; +import { verify } from "https://deno.land/x/djwt@v2.2/mod.ts"; +import * as cache from "@lib/cache/performance.ts"; import { JWT_SECRET } from "@lib/env.ts"; export async function handler( - _req: Request, + req: Request, ctx: MiddlewareHandlerContext, ) { try { - ctx.state.flag = true; - const allCookies = getCookies(_req.headers); + performance.mark("a"); + const allCookies = getCookies(req.headers); const sessionCookie = allCookies["session_cookie"]; if (!ctx.state.session && sessionCookie && JWT_SECRET) { try { @@ -22,9 +22,15 @@ export async function handler( } } catch (_err) { // + console.log({ _err }); } } - return await ctx.next(); + + const resp = await ctx.next(); + performance.mark("b"); + const b = performance.measure("a->b", "a", "b"); + cache.savePerformance(req.url, b.duration); + return resp; } catch (error) { console.error("Error", error); diff --git a/routes/admin/performance/api.ts b/routes/admin/performance/api.ts new file mode 100644 index 0000000..0bb527e --- /dev/null +++ b/routes/admin/performance/api.ts @@ -0,0 +1,9 @@ +import { Handlers } from "$fresh/server.ts"; +import { getPerformances } from "@lib/cache/performance.ts"; +import { json } from "@lib/helpers.ts"; + +export const handler: Handlers = { + async GET() { + return json(await getPerformances()); + }, +}; diff --git a/routes/admin/performance/index.tsx b/routes/admin/performance/index.tsx new file mode 100644 index 0000000..ae2532f --- /dev/null +++ b/routes/admin/performance/index.tsx @@ -0,0 +1,93 @@ +import { MainLayout } from "@components/layouts/main.tsx"; +import { Handlers, PageProps } from "$fresh/server.ts"; +import { + getPerformances, + PerformancePoint, + PerformanceRes, +} from "@lib/cache/performance.ts"; +import { AccessDeniedError } from "@lib/errors.ts"; + +export const handler: Handlers = { + async GET(_, ctx) { + const performances = await getPerformances(); + if (!("session" in ctx.state)) { + throw new AccessDeniedError(); + } + + return ctx.render({ performances }); + }, +}; + +function PerformanceLine( + { maximum, data: [amount, min, average, max], url }: { + maximum: number; + url: string; + data: readonly [number, number, number, number]; + }, +) { + return ( +
+
+ {url} +
+
+ + {Math.floor(average / 1000)}ms + + + {amount}x + +
+
+
+
+
+
+
+ ); +} + +export default function Greet( + { data: { performances }, url }: PageProps<{ performances: PerformanceRes }>, +) { + return ( + +

Performance

+ + {performances.res.map((r) => { + return ( + + ); + })} +
+ ); +}