diff --git a/memento-note/app/api/metrics/route.ts b/memento-note/app/api/metrics/route.ts new file mode 100644 index 0000000..8eb4283 --- /dev/null +++ b/memento-note/app/api/metrics/route.ts @@ -0,0 +1,68 @@ +import { NextResponse } from 'next/server' +import { prisma } from '@/lib/prisma' +import { redis } from '@/lib/redis' + +export const dynamic = 'force-dynamic' + +export async function GET() { + const lines: string[] = [] + + const metric = (name: string, help: string, type: string, value: number | string, labels = '') => { + lines.push(`# HELP ${name} ${help}`) + lines.push(`# TYPE ${name} ${type}`) + lines.push(labels ? `${name}{${labels}} ${value}` : `${name} ${value}`) + } + + // Uptime + metric('memento_uptime_seconds', 'Application uptime in seconds', 'gauge', process.uptime().toFixed(2)) + + // Database + try { + const dbStart = Date.now() + const [noteCount, notebookCount, userCount] = await Promise.all([ + prisma.note.count(), + prisma.notebook.count(), + prisma.user.count(), + ]) + const dbLatency = Date.now() - dbStart + metric('memento_db_up', 'Database connectivity (1=up, 0=down)', 'gauge', 1) + metric('memento_db_latency_ms', 'Database query latency in milliseconds', 'gauge', dbLatency) + metric('memento_notes_total', 'Total number of notes', 'gauge', noteCount) + metric('memento_notebooks_total', 'Total number of notebooks', 'gauge', notebookCount) + metric('memento_users_total', 'Total number of users', 'gauge', userCount) + } catch { + metric('memento_db_up', 'Database connectivity (1=up, 0=down)', 'gauge', 0) + } + + // Redis + try { + const redisStart = Date.now() + await redis.ping() + const dbSize = await redis.dbsize() + const info = await redis.info('memory') + const redisLatency = Date.now() - redisStart + const memMatch = info.match(/used_memory:(\d+)/) + const usedMemoryBytes = memMatch ? parseInt(memMatch[1]) : 0 + metric('memento_redis_up', 'Redis connectivity (1=up, 0=down)', 'gauge', 1) + metric('memento_redis_latency_ms', 'Redis ping latency in milliseconds', 'gauge', redisLatency) + metric('memento_redis_keys_total', 'Total number of Redis keys', 'gauge', dbSize) + metric('memento_redis_memory_bytes', 'Redis used memory in bytes', 'gauge', usedMemoryBytes) + } catch { + metric('memento_redis_up', 'Redis connectivity (1=up, 0=down)', 'gauge', 0) + } + + // Node.js process memory + const mem = process.memoryUsage() + metric('memento_process_heap_used_bytes', 'Node.js heap used in bytes', 'gauge', mem.heapUsed) + metric('memento_process_heap_total_bytes', 'Node.js heap total in bytes', 'gauge', mem.heapTotal) + metric('memento_process_rss_bytes', 'Node.js RSS memory in bytes', 'gauge', mem.rss) + + const body = lines.join('\n') + '\n' + + return new NextResponse(body, { + status: 200, + headers: { + 'Content-Type': 'text/plain; version=0.0.4; charset=utf-8', + }, + }) +}