How to Monitor FreeLLM API Performance: REST Endpoints and Desktop Analytics
FreeLLM API tracks every request in a local SQLite database and exposes real-time performance metrics through REST endpoints and an Electron-based desktop dashboard, enabling you to monitor latency, token usage, success rates, and cost savings.
The tashfeenahmed/freellmapi repository provides a self-hosted LLM API server with built-in performance monitoring capabilities. Understanding how to monitor FreeLLM API performance is essential for optimizing costs, debugging failures, and ensuring service reliability. The system utilizes a dual-layer architecture where raw request data is stored in SQLite while aggregated hourly statistics power both the desktop UI and REST API.
Monitoring Architecture Overview
FreeLLM API implements a two-tier data collection system that balances detail with query performance. As implemented in tashfeenahmed/freellmapi, the server/src/lib/request-log.ts middleware captures every request in a requests table while simultaneously updating the request_hourly aggregate table. This design ensures that analytics queries remain fast even after the raw table is pruned, while the request_hourly buckets provide durable historical metrics.
The data flows from server/src/services/media.ts through the logging middleware into SQLite, then surfaces via server/src/routes/analytics.ts for HTTP clients or desktop/src/stats.ts for the local Electron interface.
REST API Analytics Endpoints
The server exposes a comprehensive analytics API under /api/analytics that returns JSON performance data. These endpoints accept a range parameter (24h, 7d, or 30d) to filter time windows.
Summary Metrics
The /summary endpoint provides high-level performance indicators including total requests, success rate, token totals, average latency, estimated cost savings, and pinned-request statistics. It also returns the timestamp of the first recorded request.
curl http://localhost:3000/api/analytics/summary
Detailed Breakdown Endpoints
For deeper analysis, query these specialized endpoints:
/by-model– Returns metrics grouped by LLM model (e.g., GPT-4, Claude)/by-platform– Breaks down usage by provider platform/timeline– Provides time-series data for charting hourly trends/error-distribution– Categorizes failures by error type/errors– Lists recent error logs with details
# Model performance for the last 24 hours
curl "http://localhost:3000/api/analytics/by-model?range=24h"
# Hourly timeline data
curl "http://localhost:3000/api/analytics/timeline?range=24h&interval=hour"
Desktop Dashboard and Tray Interface
The Electron-based desktop application provides immediate visual feedback through system tray pop-overs and a dedicated dashboard window opened by desktop/src/window.ts.
Tray Pop-over Statistics
The tray interface utilizes helper functions from desktop/src/stats.ts to query the local SQLite database directly. Key functions include:
todayStats()– Returns today's request count, token usage, and last model usedhourlyRequests()– Generates a histogram of requests per hoursuccessRateToday()– Calculates the percentage of successful requests for the current day
These functions bypass the HTTP layer and call getDb() from server/src/db/index.ts directly, ensuring instant access to local metrics.
Programmatic Access to Desktop Stats
You can reuse the desktop statistics helpers in custom scripts by importing from the desktop source:
import { todayStats, hourlyRequests, formatTokens, successRateToday } from '../desktop/src/stats.js';
const stats = todayStats();
console.log(`Requests today: ${stats.requests}`);
console.log(`Tokens today: ${formatTokens(stats.tokens)}`);
console.log(`Success rate: ${await successRateToday()}%`);
console.log('Hourly histogram:', hourlyRequests());
Database Schema and Aggregation Strategy
The monitoring system relies on SQLite for lightweight, in-process storage. The requests table captures individual request details including model name, token counts, latency milliseconds, and success status. However, analytics queries primarily target the request_hourly aggregate table, which the logging middleware updates transactionally alongside each request insert.
This aggregation strategy prevents full-table scans during analytics queries. When you request data via the REST API or desktop helpers, the system queries the pre-aggregated hourly buckets for historical data, falling back to raw rows only for recent unaggregated entries.
Extending Analytics with Custom Metrics
The modular architecture in server/src/routes/analytics.ts supports custom metric endpoints. For example, to expose requests-per-minute (RPM) calculations:
// Add to server/src/routes/analytics.ts
analyticsRouter.get('/rpm', (req, res) => {
const range = (req.query.range as string) ?? '7d';
const since = getSinceTimestamp(range);
const rows = getDb().prepare(`
SELECT strftime('%Y-%m-%dT%H:%M:00', hour) AS minute,
SUM(total_requests) / 60.0 AS rpm
FROM request_hourly
WHERE hour >= ?
GROUP BY minute
ORDER BY minute ASC
`).all(since);
res.json(rows);
});
After restarting the server, GET /api/analytics/rpm returns per-minute request rates derived from the hourly aggregate data.
Summary
- FreeLLM API stores all request metadata in a local SQLite database managed through
server/src/db/index.ts - Hourly aggregation via the
request_hourlytable ensures fast analytics queries even with large datasets - REST endpoints under
/api/analyticsprovide JSON data for external monitoring tools, supporting ranges of 24 hours, 7 days, or 30 days - Desktop interface uses
desktop/src/stats.tshelpers to read the database directly for instant tray pop-over statistics - Key files include
server/src/lib/request-log.tsfor data collection,server/src/routes/analytics.tsfor API routes, andserver/src/services/media.tsfor request insertion
Frequently Asked Questions
How does FreeLLM API store performance data?
FreeLLM API uses a lightweight SQLite database that runs in-process with the server. Every request passes through the logging middleware in server/src/lib/request-log.ts, which inserts a row into the requests table and updates the request_hourly aggregate within the same transaction. This dual-write approach ensures data consistency while optimizing query performance for analytics.
What is the difference between the requests table and request_hourly?
The requests table contains granular data for individual API calls, including model names, token counts, and latency measurements. The request_hourly table stores pre-aggregated buckets grouped by hour, containing summed totals and averages. Analytics queries prefer the hourly table to avoid expensive full-table scans, while the raw table supports detailed debugging and can be pruned independently.
Can I access analytics without using the desktop UI?
Yes. The REST API exposed in server/src/routes/analytics.ts provides full access to performance metrics without requiring the Electron desktop application. You can query endpoints like /api/analytics/summary or /api/analytics/by-model using standard HTTP clients such as curl, Python's requests library, or monitoring tools like Grafana.
How do I calculate custom metrics like requests per minute?
Create new routes in server/src/routes/analytics.ts that query the request_hourly table. Since hourly aggregates contain total_requests per hour, you can derive per-minute estimates by dividing by 60.0 and grouping by minute using SQLite's strftime function. Register the new route with the analytics router and restart the server to expose the endpoint.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →