Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api-client/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"@preact/signals": "npm:@preact/signals@^2.9.0"
},
"name": "@01edu/api-client",
"version": "0.2.3",
"version": "0.2.4",
"license": "MIT",
"exports": { ".": "./mod.ts" },
"compilerOptions": {
Expand Down
2 changes: 1 addition & 1 deletion api/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"@std/http": "jsr:@std/http@^1.0.25"
},
"name": "@01edu/api",
"version": "0.2.3",
"version": "0.2.4",
"license": "MIT",
"exports": {
"./context": "./context.ts",
Expand Down
27 changes: 25 additions & 2 deletions api/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { respond } from './response.ts'
import type { RequestContext } from '@01edu/types/context'
import { route } from './router.ts'
import { ARR, NUM, OBJ, optional, STR } from './validator.ts'
import type { Metric, Sql } from '@01edu/types/db'
import type { Metric as DbMetric, Sql } from '@01edu/types/db'
import { routeMetrics } from './route-metrics.ts'

/**
* Authorizes access to developer routes.
Expand Down Expand Up @@ -64,7 +65,7 @@ export const createSqlDevRoute = (sql?: Sql) => {
*
* @returns A route handler configuration.
*/
export const createQueryMetricsDevRoute = (metrics: Metric[]) =>
export const createQueryMetricsDevRoute = (metrics: DbMetric[]) =>
route({
authorize: authorizeDevAccess,
fn: () => metrics,
Expand All @@ -91,9 +92,31 @@ export const createQueryMetricsDevRoute = (metrics: Metric[]) =>
run: NUM('Number of statement runs'),
filterHit: NUM('Bloom filter bypass hits'),
filterMiss: NUM('Bloom filter misses'),
memused: NUM('Approximate memory used by the statement'),
}, 'SQLite sqlite3_stmt_status counters'),
}),
'Collected query metrics',
),
description: 'List collected SQL query metrics',
})

export const createRouterMetricsDevRoute = () =>
route({
authorize: authorizeDevAccess,
fn: () => Object.values(routeMetrics),
output: ARR(
OBJ({
key: STR('Route key (method:path) allow to identify which route'),
duration: NUM(
'Total time the route handler take to respond, in milliseconds',
),
count: NUM('How many times the route was called'),
error: NUM('Number of time it responded with a status 400 or above'),
success: NUM(
'Number of time it responded with a status under 399 (Success, Redirect and Info)',
),
}, 'Route metrics'),
'Collected route metrics',
),
description: 'List collected route metrics',
})
10 changes: 10 additions & 0 deletions api/route-metrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* Imported by both the server and dev routes.
* Defined in a distinct module to avoid import loops
*/
export const routeMetrics: Record<string, {
key: string
duration: number
count: number
success: number
error: number
}> = {}
10 changes: 9 additions & 1 deletion api/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ import type {
import type { Log } from './log.ts'
import { respond, ResponseError } from './response.ts'
import type { Metric, Sql } from '@01edu/types/db'
import { createQueryMetricsDevRoute, createSqlDevRoute } from './dev.ts'
import {
createQueryMetricsDevRoute,
createRouterMetricsDevRoute,
createSqlDevRoute,
} from './dev.ts'
import { createDocRoute } from './doc.ts'
import { createHealthRoute } from './health.ts'

Expand Down Expand Up @@ -129,6 +133,10 @@ export const makeRouter = <T extends GenericRoutes>(
defs['GET/api/sql/metrics'] = createQueryMetricsDevRoute(metrics)
}

if (!defs['GET/api/router/metrics']) {
defs['GET/api/router/metrics'] = createRouterMetricsDevRoute()
}

if (!defs['GET/api/doc']) {
defs['GET/api/doc'] = createDocRoute(defs)
}
Expand Down
11 changes: 10 additions & 1 deletion api/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { respond, ResponseError } from './response.ts'
import { now } from '@01edu/time'
import type { Awaitable } from '@01edu/types'
import { BASE_URL } from './env.ts'
import { routeMetrics as metrics } from './route-metrics.ts'

type Handler = (ctx: RequestContext) => Awaitable<Response>
/**
Expand All @@ -56,7 +57,8 @@ export const server = (
): (req: Request, url?: URL) => Promise<Response> => {
const handleRequest = async (ctx: RequestContext) => {
const logProps: Record<string, unknown> = {}
logProps.path = `${ctx.req.method}:${ctx.url.pathname}`
const key = `${ctx.req.method}:${ctx.url.pathname}`
logProps.path = key
log.info('in', logProps)
try {
const res = await routeHandler(ctx)
Expand All @@ -78,6 +80,13 @@ export const server = (
logProps.duration = now() - ctx.span!
log.error('out', logProps)
return response
} finally {
const metric = metrics[key] ||
(metrics[key] = { key, duration: 0, count: 0, success: 0, error: 0 })
metric.duration += (logProps.duration as number) || 0
const status = (logProps.status as number) || 0
metric[status > 399 ? 'error' : 'success']++
metric.count++
}
}

Expand Down
2 changes: 1 addition & 1 deletion db/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"@std/assert": "jsr:@std/assert@^1.0.19"
},
"name": "@01edu/db",
"version": "0.2.3",
"version": "0.2.4",
"license": "MIT",
"exports": {
".": "./mod.ts",
Expand Down
3 changes: 1 addition & 2 deletions db/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import { assertEquals } from '@std/assert/equals'
import { Database, type BindParameters, type BindValue } from '@db/sqlite'
import { type BindParameters, type BindValue, Database } from '@db/sqlite'
import type { Expand, MatchKeys, UnionToIntersection } from '@01edu/types'
import type { ExplainRow, Metric, Sql } from '@01edu/types/db'
import { respond } from '@01edu/api/response'
Expand Down Expand Up @@ -518,4 +518,3 @@ export const sqlCheck = <T extends BindValue | BindParameters>(
const { value } = sql`SELECT EXISTS(SELECT 1 ${String.raw(query, ...args)})`
return ((params: T) => value(params)?.[0] === 1)
}

2 changes: 1 addition & 1 deletion signal-router/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"preact": "npm:preact@^10.29.0"
},
"name": "@01edu/signal-router",
"version": "0.2.1",
"version": "0.2.2",
"license": "MIT",
"exports": { ".": "./mod.tsx" },
"compilerOptions": {
Expand Down
4 changes: 2 additions & 2 deletions signal-router/mod.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import type {
TargetedMouseEvent,
TargetedPointerEvent,
} from 'preact'
import { computed, Signal } from '@preact/signals'
import { computed, Signal, untracked } from '@preact/signals'

const isCurrentURL = (alt: URL) => {
const url = urlSignal.value
Expand Down Expand Up @@ -138,7 +138,7 @@ const getUrl = ({ href, hash, params }: GetUrlProps): URL => {
* ```
*/
export const navigate = (props: GetUrlProps & { replace?: boolean }): void =>
navigateUrl(getUrl(props).href, props.replace)
untracked(() => navigateUrl(getUrl(props).href, props.replace))

/**
* Props for the `<A>` component.
Expand Down
2 changes: 1 addition & 1 deletion types/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"@db/sqlite": "jsr:@cd/sqlite@^0.13.1"
},
"name": "@01edu/types",
"version": "0.2.3",
"version": "0.2.4",
"license": "MIT",
"exports": {
".": "./mod.d.ts",
Expand Down
6 changes: 6 additions & 0 deletions types/router.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ type ReservedRoutes<Session = any> = {
* @deprecated
*/
'GET/api/sql/metrics'?: Handler<Session, Def | undefined, Def | undefined>

/**
* ⚠️ WARNING: You are overriding the system router metrics route.
* @deprecated
*/
'GET/api/router/metrics'?: Handler<Session, Def | undefined, Def | undefined>
}

// deno-lint-ignore no-explicit-any
Expand Down