* Return metrics for the last N days: writes per day (from chunks), viewer calls per day.
(days: number)
| 814 | * Return metrics for the last N days: writes per day (from chunks), viewer calls per day. |
| 815 | */ |
| 816 | getMetrics(days: number): { |
| 817 | writesPerDay: Array<{ date: string; count: number }>; |
| 818 | viewerCallsPerDay: Array<{ date: string; list: number; search: number; total: number }>; |
| 819 | totals: { memories: number; sessions: number; embeddings: number; todayWrites: number; todayViewerCalls: number }; |
| 820 | } { |
| 821 | const since = Date.now() - days * 86400 * 1000; |
| 822 | const now = new Date(); |
| 823 | const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime(); |
| 824 | |
| 825 | const writesRows = this.db |
| 826 | .prepare( |
| 827 | `SELECT date(created_at/1000, 'unixepoch', 'localtime') as d, COUNT(*) as c |
| 828 | FROM chunks WHERE created_at >= ? GROUP BY d ORDER BY d`, |
| 829 | ) |
| 830 | .all(since) as Array<{ d: string; c: number }>; |
| 831 | const writesPerDay = writesRows.map((r) => ({ date: r.d, count: r.c })); |
| 832 | |
| 833 | const eventsRows = this.db |
| 834 | .prepare( |
| 835 | `SELECT date(created_at/1000, 'unixepoch', 'localtime') as d, event_type, COUNT(*) as c |
| 836 | FROM viewer_events WHERE created_at >= ? GROUP BY d, event_type ORDER BY d`, |
| 837 | ) |
| 838 | .all(since) as Array<{ d: string; event_type: string; c: number }>; |
| 839 | const byDate = new Map<string, { list: number; search: number }>(); |
| 840 | for (const r of eventsRows) { |
| 841 | let row = byDate.get(r.d); |
| 842 | if (!row) { |
| 843 | row = { list: 0, search: 0 }; |
| 844 | byDate.set(r.d, row); |
| 845 | } |
| 846 | if (r.event_type === "list") row.list += r.c; |
| 847 | else if (r.event_type === "search") row.search += r.c; |
| 848 | } |
| 849 | const viewerCallsPerDay = Array.from(byDate.entries()) |
| 850 | .sort((a, b) => a[0].localeCompare(b[0])) |
| 851 | .map(([date, v]) => ({ date, list: v.list, search: v.search, total: v.list + v.search })); |
| 852 | |
| 853 | const totalChunks = (this.db.prepare("SELECT COUNT(*) as c FROM chunks").get() as { c: number }).c; |
| 854 | const totalSessions = (this.db.prepare("SELECT COUNT(DISTINCT session_key) as c FROM chunks").get() as { c: number }).c; |
| 855 | const totalEmbeddings = (this.db.prepare("SELECT COUNT(*) as c FROM embeddings").get() as { c: number }).c; |
| 856 | const todayWrites = (this.db.prepare("SELECT COUNT(*) as c FROM chunks WHERE created_at >= ?").get(todayStart) as { c: number }).c; |
| 857 | const todayViewerCalls = (this.db.prepare("SELECT COUNT(*) as c FROM viewer_events WHERE created_at >= ?").get(todayStart) as { c: number }).c; |
| 858 | |
| 859 | return { |
| 860 | writesPerDay, |
| 861 | viewerCallsPerDay, |
| 862 | totals: { |
| 863 | memories: totalChunks, |
| 864 | sessions: totalSessions, |
| 865 | embeddings: totalEmbeddings, |
| 866 | todayWrites, |
| 867 | todayViewerCalls, |
| 868 | }, |
| 869 | }; |
| 870 | } |
| 871 | |
| 872 | |
| 873 | private migrateHubTables(): void { |