# Metrics and Tracing Moltis includes comprehensive observability support through Prometheus metrics and tracing integration. This document explains how to enable, configure, and use these features. ## Overview The metrics system is built on the [`metrics`](https://docs.rs/metrics) crate facade, which provides a unified interface similar to the `log` crate. When the `prometheus` feature is enabled, metrics are exported in Prometheus text format for scraping by Grafana, Prometheus, or other monitoring tools. All metrics are **feature-gated** — they add zero overhead when disabled. ## Feature Flags Metrics are controlled by two feature flags: | Feature | Description | Default | |---------|-------------|---------| | `metrics` | Enables metrics collection and the `/api/metrics` JSON API | Enabled | | `prometheus` | Enables the `/metrics` Prometheus endpoint (requires `metrics`) | Enabled | ### Compile-Time Configuration ```toml # Enable only metrics collection (no Prometheus endpoint) moltis-gateway = { version = "0.1", features = ["metrics"] } # Enable metrics with Prometheus export (default) moltis-gateway = { version = "0.1", features = ["metrics", "prometheus"] } # Enable metrics for specific crates moltis-agents = { version = "0.1", features = ["metrics"] } moltis-cron = { version = "0.1", features = ["metrics"] } ``` To build without metrics entirely: ```bash cargo build --release --no-default-features --features "file-watcher,tailscale,tls,web-ui" ``` ## Prometheus Endpoint When the `prometheus` feature is enabled, the gateway exposes a `/metrics` endpoint: ``` GET http://localhost:18789/metrics ``` This endpoint is **unauthenticated** to allow Prometheus scrapers to access it. It returns metrics in Prometheus text format: ``` # HELP moltis_http_requests_total Total number of HTTP requests handled # TYPE moltis_http_requests_total counter moltis_http_requests_total{method="GET",status="200",endpoint="/api/chat"} 42 # HELP moltis_llm_completion_duration_seconds Duration of LLM completion requests # TYPE moltis_llm_completion_duration_seconds histogram moltis_llm_completion_duration_seconds_bucket{provider="anthropic",model="claude-3-opus",le="1.0"} 5 ``` ### Grafana Integration To scrape metrics with Prometheus and visualize in Grafana: 1. Add moltis to your `prometheus.yml`: ```yaml scrape_configs: - job_name: 'moltis' static_configs: - targets: ['localhost:18789'] metrics_path: /metrics scrape_interval: 15s ``` 2. Import or create Grafana dashboards using the `moltis_*` metrics. ## JSON API Endpoints For the web UI dashboard and programmatic access, authenticated JSON endpoints are available: | Endpoint | Description | |----------|-------------| | `GET /api/metrics` | Full metrics snapshot with aggregates and per-provider breakdown | | `GET /api/metrics/summary` | Lightweight counts for navigation badges | | `GET /api/metrics/history` | Time-series data points for charts (last hour, 10s intervals) | ### History Endpoint The `/api/metrics/history` endpoint returns historical metrics data for rendering time-series charts: ```json { "enabled": true, "interval_seconds": 10, "max_points": 60480, "points": [ { "timestamp": 1706832000000, "llm_completions": 42, "llm_input_tokens": 15000, "llm_output_tokens": 8000, "http_requests": 150, "ws_active": 3, "tool_executions": 25, "mcp_calls": 12, "active_sessions": 2 } ] } ``` ## Metrics Persistence Metrics history is persisted to SQLite, so historical data survives server restarts. The database is stored at `~/.moltis/metrics.db` (or the configured data directory). Key features: - **7-day retention**: History is kept for 7 days (60,480 data points at 10-second intervals) - **Automatic cleanup**: Old data is automatically removed hourly - **Startup recovery**: History is loaded from the database when the server starts The storage backend uses a trait-based design (`MetricsStore`), allowing alternative implementations (e.g., TimescaleDB) for larger deployments. ### Storage Architecture ```rust // The MetricsStore trait defines the storage interface #[async_trait] pub trait MetricsStore: Send + Sync { async fn save_point(&self, point: &MetricsHistoryPoint) -> Result<()>; async fn load_history(&self, since: u64, limit: usize) -> Result>; async fn cleanup_before(&self, before: u64) -> Result; async fn latest_point(&self) -> Result>; } ``` The default `SqliteMetricsStore` implementation stores data in a single table with an index on the timestamp column for efficient range queries. ## Web UI Dashboard The gateway includes a built-in metrics dashboard at `/monitoring` in the web UI. This page displays: **Overview Tab:** - System metrics (uptime, connected clients, active sessions) - LLM usage (completions, tokens, cache statistics) - Tool execution statistics - MCP server status - Provider breakdown table - Prometheus endpoint (with copy button) **Charts Tab:** - Token usage over time (input/output) - HTTP requests and LLM completions - WebSocket connections and active sessions - Tool executions and MCP calls The dashboard uses [uPlot](https://github.com/leeoniya/uPlot) for lightweight, high-performance time-series charts. Data updates every 10 seconds for current metrics and every 30 seconds for history. ## Available Metrics ### HTTP Metrics | Metric | Type | Labels | Description | |--------|------|--------|-------------| | `moltis_http_requests_total` | Counter | method, status, endpoint | Total HTTP requests | | `moltis_http_request_duration_seconds` | Histogram | method, status, endpoint | Request latency | | `moltis_http_requests_in_flight` | Gauge | — | Currently processing requests | ### LLM/Agent Metrics | Metric | Type | Labels | Description | |--------|------|--------|-------------| | `moltis_llm_completions_total` | Counter | provider, model | Total completions requested | | `moltis_llm_completion_duration_seconds` | Histogram | provider, model | Completion latency | | `moltis_llm_input_tokens_total` | Counter | provider, model | Input tokens processed | | `moltis_llm_output_tokens_total` | Counter | provider, model | Output tokens generated | | `moltis_llm_completion_errors_total` | Counter | provider, model, error_type | Completion failures | | `moltis_llm_time_to_first_token_seconds` | Histogram | provider, model | Streaming TTFT | #### Provider Aliases When you have multiple instances of the same provider type (e.g., separate API keys for work and personal use), you can use the `alias` configuration option to differentiate them in metrics: ```toml [providers.anthropic] api_key = "sk-work-..." alias = "anthropic-work" # Note: You would need separate config sections for multiple instances # of the same provider. This is a placeholder for future functionality. ``` The alias appears in the `provider` label of all LLM metrics: ``` moltis_llm_input_tokens_total{provider="anthropic-work", model="claude-3-opus"} 5000 moltis_llm_input_tokens_total{provider="anthropic-personal", model="claude-3-opus"} 3000 ``` This allows you to: - Track token usage separately for billing purposes - Create separate Grafana dashboards per provider instance - Monitor rate limits and quotas independently ### MCP (Model Context Protocol) Metrics | Metric | Type | Labels | Description | |--------|------|--------|-------------| | `moltis_mcp_tool_calls_total` | Counter | server, tool | Tool invocations | | `moltis_mcp_tool_call_duration_seconds` | Histogram | server, tool | Tool call latency | | `moltis_mcp_tool_call_errors_total` | Counter | server, tool, error_type | Tool call failures | | `moltis_mcp_servers_connected` | Gauge | — | Active MCP server connections | ### Tool Execution Metrics | Metric | Type | Labels | Description | |--------|------|--------|-------------| | `moltis_tool_executions_total` | Counter | tool | Tool executions | | `moltis_tool_execution_duration_seconds` | Histogram | tool | Execution time | | `moltis_sandbox_command_executions_total` | Counter | — | Sandbox commands run | ### Session Metrics | Metric | Type | Labels | Description | |--------|------|--------|-------------| | `moltis_sessions_created_total` | Counter | — | Sessions created | | `moltis_sessions_active` | Gauge | — | Currently active sessions | | `moltis_session_messages_total` | Counter | role | Messages by role | ### Cron Job Metrics | Metric | Type | Labels | Description | |--------|------|--------|-------------| | `moltis_cron_jobs_scheduled` | Gauge | — | Number of scheduled jobs | | `moltis_cron_executions_total` | Counter | — | Job executions | | `moltis_cron_execution_duration_seconds` | Histogram | — | Job duration | | `moltis_cron_errors_total` | Counter | — | Failed jobs | | `moltis_cron_stuck_jobs_cleared_total` | Counter | — | Jobs exceeding 2h timeout | | `moltis_cron_input_tokens_total` | Counter | — | Input tokens from cron runs | | `moltis_cron_output_tokens_total` | Counter | — | Output tokens from cron runs | ### Memory/Search Metrics | Metric | Type | Labels | Description | |--------|------|--------|-------------| | `moltis_memory_searches_total` | Counter | search_type | Searches performed | | `moltis_memory_search_duration_seconds` | Histogram | search_type | Search latency | | `moltis_memory_embeddings_generated_total` | Counter | provider | Embeddings created | ### Channel Metrics | Metric | Type | Labels | Description | |--------|------|--------|-------------| | `moltis_channels_active` | Gauge | — | Loaded channel plugins | | `moltis_channel_messages_received_total` | Counter | channel | Inbound messages | | `moltis_channel_messages_sent_total` | Counter | channel | Outbound messages | | `moltis_channel_otp_challenges_total` | Counter | channel | OTP challenges issued to non-allowlisted users | | `moltis_channel_otp_verifications_total` | Counter | channel, result | OTP verification attempts (`approved`, `wrong_code`, `locked_out`, `expired`) | ### Telegram-Specific Metrics | Metric | Type | Labels | Description | |--------|------|--------|-------------| | `moltis_telegram_messages_received_total` | Counter | — | Messages from Telegram | | `moltis_telegram_access_control_denials_total` | Counter | — | Access denied events | | `moltis_telegram_polling_duration_seconds` | Histogram | — | Message handling time | ### OAuth Metrics | Metric | Type | Labels | Description | |--------|------|--------|-------------| | `moltis_oauth_flow_starts_total` | Counter | — | OAuth flows initiated | | `moltis_oauth_flow_completions_total` | Counter | — | Successful completions | | `moltis_oauth_token_refresh_total` | Counter | — | Token refreshes | | `moltis_oauth_token_refresh_failures_total` | Counter | — | Refresh failures | ### Skills Metrics | Metric | Type | Labels | Description | |--------|------|--------|-------------| | `moltis_skills_installation_attempts_total` | Counter | — | Installation attempts | | `moltis_skills_installation_duration_seconds` | Histogram | — | Installation time | | `moltis_skills_git_clone_total` | Counter | — | Successful git clones | | `moltis_skills_git_clone_fallback_total` | Counter | — | Fallbacks to HTTP tarball | ## Tracing Integration The `moltis-metrics` crate includes optional tracing integration via the `tracing` feature. This allows span context to propagate to metric labels. ### Enabling Tracing ```toml moltis-metrics = { version = "0.1", features = ["prometheus", "tracing"] } ``` ### Initialization ```rust use moltis_metrics::tracing_integration::init_tracing; fn main() { // Initialize tracing with metrics context propagation init_tracing(); // Now spans will add labels to metrics } ``` ### How It Works When tracing is enabled, span fields are automatically added as metric labels: ```rust use tracing::instrument; #[instrument(fields(operation = "fetch_user", component = "api"))] async fn fetch_user(id: u64) -> User { // Metrics recorded here will include: // - operation="fetch_user" // - component="api" counter!("api_calls_total").increment(1); } ``` ### Span Labels The following span fields are propagated to metrics: | Field | Description | |-------|-------------| | `operation` | The operation being performed | | `component` | The component/module name | | `span.name` | The span's target/name | ## Adding Custom Metrics ### In Your Code Use the `metrics` macros re-exported from `moltis-metrics`: ```rust use moltis_metrics::{counter, gauge, histogram, labels}; // Simple counter counter!("my_custom_requests_total").increment(1); // Counter with labels counter!( "my_custom_requests_total", labels::ENDPOINT => "/api/users", labels::METHOD => "GET" ).increment(1); // Gauge (current value) gauge!("my_queue_size").set(42.0); // Histogram (distribution) histogram!("my_operation_duration_seconds").record(0.123); ``` ### Feature-Gating Always gate metrics code to avoid overhead when disabled: ```rust #[cfg(feature = "metrics")] use moltis_metrics::{counter, histogram}; pub async fn my_function() { #[cfg(feature = "metrics")] let start = std::time::Instant::now(); // ... do work ... #[cfg(feature = "metrics")] { counter!("my_operations_total").increment(1); histogram!("my_operation_duration_seconds") .record(start.elapsed().as_secs_f64()); } } ``` ### Adding New Metric Definitions For consistency, add metric name constants to `crates/metrics/src/definitions.rs`: ```rust /// My feature metrics pub mod my_feature { /// Total operations performed pub const OPERATIONS_TOTAL: &str = "moltis_my_feature_operations_total"; /// Operation duration in seconds pub const OPERATION_DURATION_SECONDS: &str = "moltis_my_feature_operation_duration_seconds"; } ``` Then use them: ```rust use moltis_metrics::{counter, my_feature}; counter!(my_feature::OPERATIONS_TOTAL).increment(1); ``` ## Configuration Metrics configuration in `moltis.toml`: ```toml [metrics] enabled = true # Enable metrics collection (default: true) prometheus_endpoint = true # Expose /metrics endpoint (default: true) labels = { env = "prod" } # Add custom labels to all metrics ``` Environment variables: - `RUST_LOG=moltis_metrics=debug` — Enable debug logging for metrics initialization ## Best Practices 1. **Use consistent naming**: Follow the pattern `moltis___` 2. **Add units to names**: `_total` for counters, `_seconds` for durations, `_bytes` for sizes 3. **Keep cardinality low**: Avoid high-cardinality labels (like user IDs or request IDs) 4. **Feature-gate everything**: Use `#[cfg(feature = "metrics")]` to ensure zero overhead when disabled 5. **Use predefined buckets**: The `buckets` module has standard histogram buckets for common metric types ## Troubleshooting ### Metrics not appearing 1. Verify the `metrics` feature is enabled at compile time 2. Check that the metrics recorder is initialized (happens automatically in gateway) 3. Ensure you're hitting the correct `/metrics` endpoint 4. Check `moltis.toml` has `[metrics] enabled = true` ### Prometheus endpoint not available 1. Ensure the `prometheus` feature is enabled (it's separate from `metrics`) 2. Check your build: `cargo build --features prometheus` ### High memory usage - Check for high-cardinality labels (many unique label combinations) - Consider reducing histogram bucket counts ### Missing labels - Ensure labels are passed consistently across all metric recordings - Check that tracing spans include the expected fields