Time-Series Metrics System
DeployStack includes a generic time-series metrics system for collecting and querying bucketed activity data. This guide shows you how to add new metric types (like server installations, tool executions, satellite health, etc.) following the established pattern.Architecture Overview
The metrics system uses a generic base service with specific implementations pattern:- Base Service: Generic time-series query logic (bucket generation, gap filling, response formatting)
- Metric Service: Table-specific query logic (extends base service)
- Database Table: Stores pre-aggregated time buckets
- Data Collection: Populates buckets (event handlers, cron jobs, etc.)
- Cleanup System: Removes old data based on retention policy
- API Endpoint: Exposes metrics via REST API with permission checks
- Permissions: Role-based access control for metrics viewing
Reference Implementation
MCP Client Activity Metrics serves as the complete reference implementation. All files are in place and can be used as templates for new metric types. Key Files:- Database:
src/db/schema.sqlite.ts
(table:mcpClientActivityMetrics
) - Base Service:
src/services/metrics/TimeSeriesMetricsService.ts
- Metric Service:
src/services/metrics/McpClientActivityMetricsService.ts
- Event Handler:
src/events/satellite/mcp-client-activity.ts
- Cleanup Worker:
src/workers/mcpClientActivityMetricsCleanupWorker.ts
- Cron Job:
src/cron/jobs/mcpClientActivityMetricsCleanup.ts
- API Endpoint:
src/routes/users/me/metrics/mcp/client-activity.ts
- Permissions:
src/permissions/index.ts
(permission:metrics.mcp_client_activity_metrics.view
)
Adding a New Metric Type
Follow these steps to add a new metric type to the system. Each step includes code examples you can copy and modify.Step 1: Define Your Metric Requirements
Before writing code, answer these questions: Data Collection:- What events/actions trigger data collection?
- What counters will you track? (requests, tool calls, errors, duration, etc.)
- What dimensions do you need? (user_id, team_id, server_id, etc.)
- How frequently will data be collected? (every 30 seconds, on user action, etc.)
- What bucket intervals? (
15m
,1h
,1d
) - What retention period? (3 days, 30 days, 90 days)
- What cleanup frequency? (every 30 minutes, daily, weekly)
- Who can view the metrics? (users viewing their own, admins viewing all)
- What filters are needed? (by team, by server, by satellite, etc.)
- What time ranges? (1h, 3h, 24h, 7d, 30d)
Step 2: Create Database Table
Create your metrics table insrc/db/schema.sqlite.ts
following the mcpClientActivityMetrics
table pattern.
Critical Requirements:
- Use
bucket_timestamp
(integer, Unix seconds) - Use
bucket_interval
with enum constraint - Include
created_at
timestamp - Add composite unique constraint for UPSERT
- Create indexes for common query patterns
- Use foreign keys with
onDelete: 'cascade'
- Use snake_case for all field names
Step 3: Generate and Apply Migration
Generate your migration:Step 4: Create Metric Service
Create a service that extends the baseTimeSeriesMetricsService
:
File: src/services/metrics/ServerInstallMetricsService.ts
- Extends
TimeSeriesMetricsService
- Implements
getMetricType()
andqueryBuckets()
- Uses
SUM()
andGROUP BY
for aggregation - Handles optional filters (server_id, satellite_id, etc.)
- Orchestrates full flow in public method
Step 5: Add Permissions
Add the metric permission tosrc/permissions/index.ts
:
metrics.<table_name>.view
For detailed information about the permission system, role hierarchy, and access control patterns, see Role Management.
Step 6: Create API Endpoint
Create your API endpoint following the established patterns: File Structure:src/routes/users/me/metrics/your-metric.ts
Key Requirements:
- Use
preValidation
for authorization (requires permission check) - Accept query parameters:
team_id
(required),time_range
,interval
, optional filters - Return standardized time-series response from service
- Manual JSON serialization with
JSON.stringify()
src/routes/users/me/metrics/mcp/client-activity.ts
Complete Patterns:
- Authorization: API Security Best Practices
- Request/Response schemas: API Documentation
- Query parameters: Use reusable schema constants pattern from reference implementation
src/routes/users/me/metrics/index.ts
.
Step 7: Set Up Data Collection
Write data to your metrics table from the appropriate source. Common patterns:Pattern A: Event Handler (Real-Time Collection)
For real-time metrics collection from satellite events, follow themcp-client-activity.ts
event handler pattern:
- Calculate bucket timestamps for your intervals (15m, 1h)
- Use UPSERT with composite unique constraint
- Write to multiple bucket intervals simultaneously
- Non-fatal error handling (log errors, don’t block event processing)
src/events/satellite/mcp-client-activity.ts
Pattern B: Cron Job (Periodic Aggregation)
For periodic aggregation from existing data, use the cron + worker pattern:- Create cron job definition in
src/cron/jobs/
- Create worker in
src/workers/
to implement aggregation logic - Register both in
src/cron/index.ts
andsrc/workers/index.ts
Step 8: Add Cleanup System
Create a cleanup worker and cron job following the MCP client activity cleanup pattern: Key Components:- Cleanup worker in
src/workers/
implementing theWorker
interface - Cron job definition in
src/cron/jobs/
scheduling the cleanup - Registration in
src/workers/index.ts
andsrc/cron/index.ts
- Calculate cutoff timestamp based on retention period
- Delete old buckets using time-based index:
lt(table.bucket_timestamp, cutoffTimestamp)
- Handle both database drivers:
(result.changes || result.rowsAffected || 0)
- Log deletion statistics for monitoring
- Worker:
src/workers/mcpClientActivityMetricsCleanupWorker.ts
- Cron Job:
src/cron/jobs/mcpClientActivityMetricsCleanup.ts
Metrics-Specific Patterns
Bucket Timestamp Calculation
Always round down to bucket boundaries for alignment:Service Aggregation Pattern
Always aggregate multiple records per bucket usingSUM()
and GROUP BY
:
Database Driver Compatibility
Handle both SQLite (changes
) and Turso (rowsAffected
):
Common Pitfalls
❌ Using milliseconds instead of seconds
❌ Not aggregating buckets
❌ Not handling both database drivers
Related Documentation
- Database Management - Schema design, migrations, Drizzle ORM
- API Security - Authorization patterns and security best practices
- Role Management - Permission system details
- Background Job Queue - Worker and job queue system
- Cron Scheduling - Scheduled task management
Summary
Adding a new metric type involves:- Define requirements (counters, dimensions, intervals, retention)
- Create database table with proper indexes and constraints
- Generate and apply migration
- Create metric service extending
TimeSeriesMetricsService
- Add permissions to roles system
- Create API endpoint with security checks
- Set up data collection (event handlers or cron jobs)
- Add cleanup system (worker + cron job)