Ctrl K
ComunidadGuías…

docs/PROMETHEUS.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Prometheus Metrics Export

This document describes the Prometheus metrics export feature that enables comprehensive monitoring and observability for Actual-sync.

## Table of Contents

- [Overview](#overview)
- [Features](#features)
- [Configuration](#configuration)
- [Available Metrics](#available-metrics)
- [Accessing Metrics](#accessing-metrics)
- [Prometheus Setup](#prometheus-setup)
- [Grafana Dashboard](#grafana-dashboard)
- [Alerting Examples](#alerting-examples)
- [Troubleshooting](#troubleshooting)

## Overview

Actual-sync exposes Prometheus-compatible metrics that provide detailed insights into:

- Sync operation performance and reliability
- Account processing success rates
- Error patterns and distributions
- Node.js runtime health (memory, CPU, event loop)
- Historical success rates per server

These metrics enable:
- Real-time monitoring dashboards
- Automated alerting on failures
- Performance trend analysis
- Capacity planning

## Features

### Application Metrics

- **Sync Duration**: Histogram of sync operation durations with percentile tracking
- **Sync Total Counter**: Total number of syncs by server and status
- **Accounts Processed**: Number of accounts successfully synced
- **Accounts Failed**: Number of accounts that failed to sync
- **Last Sync Timestamp**: Unix timestamp of last sync per server
- **Success Rate**: Success rate per server (calculated from history)
- **Error Distribution**: Count of errors by server and error code
- **Application Info**: Version and Node.js version labels

### Node.js Runtime Metrics

When `includeDefaultMetrics` is enabled (default: true):

- **Memory Usage**: Heap and RSS memory metrics
- **CPU Usage**: Process CPU time
- **Event Loop Lag**: Event loop delay metrics
- **Garbage Collection**: GC duration and frequency
- **Active Handles**: Open file descriptors and sockets

## Configuration

Add the `prometheus` section to your `config.json`:

```json
{
  "prometheus": {
    "enabled": true,
    "includeDefaultMetrics": true
  }
}
```

### Configuration Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `enabled` | boolean | `true` | Enable Prometheus metrics export |
| `includeDefaultMetrics` | boolean | `true` | Include Node.js runtime metrics |

The scrape endpoint is open by default. To require a credential, set `healthCheck.auth` (separate from the dashboard login; see [HEALTH_CHECK.md](HEALTH_CHECK.md#which-endpoints-are-authenticated)) and add the matching `authorization` or `basic_auth` block to the scrape config below.

## Available Metrics

### Custom Application Metrics

#### `actual_sync_duration_seconds`

**Type**: Histogram  
**Labels**: `server`, `status`  
**Description**: Duration of sync operations in seconds

Buckets: `[1, 5, 10, 30, 60, 120, 300, 600]` (1s to 10min)

Use for:
- Tracking performance trends
- Identifying slow syncs
- Setting SLOs

Example queries:
```promql
# Average sync duration (5m)
rate(actual_sync_duration_seconds_sum[5m]) / rate(actual_sync_duration_seconds_count[5m])

# 95th percentile duration
histogram_quantile(0.95, rate(actual_sync_duration_seconds_bucket[5m]))

# Max duration in last hour
max_over_time(actual_sync_duration_seconds_sum[1h])
```

#### `actual_sync_total`

**Type**: Counter  
**Labels**: `server`, `status`  
**Description**: Total number of sync operations

Use for:
- Tracking sync frequency
- Success vs failure rates
- Volume trending

Example queries:
```promql
# Sync rate per hour
rate(actual_sync_total[1h])

# Total syncs by status
sum by (status) (actual_sync_total)

# Failed syncs in last 24h
increase(actual_sync_total{status="error"}[24h])
```

#### `actual_sync_accounts_processed`

**Type**: Gauge  
**Labels**: `server`  
**Description**: Number of accounts successfully processed in last sync

Use for:
- Monitoring data volume
- Detecting incomplete syncs
- Capacity planning

Example queries:
```promql
# Total accounts processed
sum(actual_sync_accounts_processed)

# Accounts per server
actual_sync_accounts_processed

# Change in accounts over time
delta(actual_sync_accounts_processed[1h])
```

#### `actual_sync_accounts_failed`

**Type**: Gauge  
**Labels**: `server`  
**Description**: Number of accounts that failed in last sync

Use for:
- Identifying problematic accounts
- Quality monitoring
- Alert triggering

Example queries:
```promql
# Any failures
actual_sync_accounts_failed > 0

# Total failures across servers
sum(actual_sync_accounts_failed)

# Failure ratio
actual_sync_accounts_failed / (actual_sync_accounts_processed + actual_sync_accounts_failed)
```

#### `actual_sync_last_sync_timestamp`

**Type**: Gauge  
**Labels**: `server`, `status`  
**Description**: Unix timestamp of last sync operation

Use for:
- Detecting stalled syncs
- Monitoring sync frequency
- Alerting on delays

Example queries:
```promql
# Time since last successful sync (seconds)
time() - actual_sync_last_sync_timestamp{status="success"}

# Servers with no recent sync (>2 hours)
(time() - actual_sync_last_sync_timestamp{status="success"}) > 7200

# Minutes since last sync
(time() - actual_sync_last_sync_timestamp{status="success"}) / 60
```

#### `actual_sync_success_rate`

**Type**: Gauge  
**Labels**: `server`  
**Description**: Success rate of sync operations (0-1)

Calculated from sync history. Updated before metrics are exported.

Use for:
- SLO tracking
- Quality monitoring
- Comparative analysis

Example queries:
```promql
# Current success rate (percentage)
actual_sync_success_rate * 100

# Servers below 95% success
actual_sync_success_rate < 0.95

# Average success rate
avg(actual_sync_success_rate)
```

#### `actual_sync_errors_total`

**Type**: Gauge  
**Labels**: `server`, `error_code`  
**Description**: Total number of sync errors by type

Use for:
- Error pattern analysis
- Root cause investigation
- Alert routing

Example queries:
```promql
# Errors by code
sum by (error_code) (actual_sync_errors_total)

# Rate limit errors
actual_sync_errors_total{error_code="RATE_LIMIT"}

# Top error codes
topk(5, sum by (error_code) (actual_sync_errors_total))
```

#### `actual_sync_info`

**Type**: Gauge  
**Labels**: `version`, `node_version`  
**Description**: Application metadata (always 1)

Use for:
- Version tracking
- Deployment verification
- Runtime information

Example query:
```promql
# Application version
actual_sync_info
```

### Default Node.js Metrics

When `includeDefaultMetrics: true`:

- `process_cpu_user_seconds_total` - CPU time in user mode
- `process_cpu_system_seconds_total` - CPU time in system mode
- `process_resident_memory_bytes` - Resident memory size
- `nodejs_heap_size_total_bytes` - Total heap size
- `nodejs_heap_size_used_bytes` - Used heap size
- `nodejs_external_memory_bytes` - External memory (C++ objects)
- `nodejs_eventloop_lag_seconds` - Event loop lag
- `nodejs_active_handles_total` - Active handles
- `nodejs_active_requests_total` - Active requests
- `nodejs_gc_duration_seconds` - GC duration by type

See [prom-client documentation](https://github.com/siimon/prom-client#default-metrics) for complete list.

## Accessing Metrics

Metrics are exposed via HTTP endpoint at `/metrics/prometheus` on the health check port (default: 3000).

### Curl Example

```bash
curl http://localhost:3000/metrics/prometheus
```

### Response Format

Prometheus text format:

```
# HELP actual_sync_duration_seconds Duration of sync operations in seconds
# TYPE actual_sync_duration_seconds histogram
actual_sync_duration_seconds_bucket{server="Main",status="success",le="1"} 0
actual_sync_duration_seconds_bucket{server="Main",status="success",le="5"} 2
actual_sync_duration_seconds_bucket{server="Main",status="success",le="10"} 5
actual_sync_duration_seconds_bucket{server="Main",status="success",le="+Inf"} 5
actual_sync_duration_seconds_sum{server="Main",status="success"} 28.5
actual_sync_duration_seconds_count{server="Main",status="success"} 5

# HELP actual_sync_total Total number of sync operations
# TYPE actual_sync_total counter
actual_sync_total{server="Main",status="success"} 42
actual_sync_total{server="Main",status="error"} 3

# HELP actual_sync_success_rate Success rate of sync operations (0-1)
# TYPE actual_sync_success_rate gauge
actual_sync_success_rate{server="Main"} 0.9333333333333333
```

### Verify Metrics

Check that metrics are accessible:

```bash
# Test endpoint
curl -s http://localhost:3000/metrics/prometheus | grep actual_sync_

# Count metrics
curl -s http://localhost:3000/metrics/prometheus | grep "^actual_sync_" | wc -l

# View specific metric
curl -s http://localhost:3000/metrics/prometheus | grep actual_sync_total
```

## Prometheus Setup

### 1. Install Prometheus

#### Docker

```bash
docker run -d \
  --name prometheus \
  -p 9090:9090 \
  -v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml \
  prom/prometheus
```

#### Docker Compose

Add to `docker-compose.yml`:

```yaml
services:
  actual-sync:
    # ... existing config ...

  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    restart: unless-stopped
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus-data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--storage.tsdb.retention.time=30d'
    networks:
      - actual-network

volumes:
  prometheus-data:

networks:
  actual-network:
    driver: bridge
```

### 2. Configure Prometheus

Create `prometheus.yml`:

```yaml
global:
  scrape_interval: 30s
  evaluation_interval: 30s
  scrape_timeout: 10s

scrape_configs:
  - job_name: 'actual-sync'
    static_configs:
      - targets: ['actual-sync:3000']
    metrics_path: '/metrics/prometheus'
    scrape_interval: 30s
```

With `healthCheck.auth` set, add the credential, or every scrape gets `401` and the target shows `DOWN`:

```yaml
scrape_configs:
  - job_name: 'actual-sync'
    static_configs:
      - targets: ['actual-sync:3000']
    metrics_path: '/metrics/prometheus'
    # healthCheck.auth.type: token
    authorization:
      type: Bearer
      credentials: a-long-random-value
    # or, for healthCheck.auth.type: basic
    # basic_auth:
    #   username: prometheus
    #   password: change-me
```

Prometheus can also read the secret from a file (`credentials_file`, `password_file`) so it stays out of `prometheus.yml`.

For multiple instances:

```yaml
scrape_configs:
  - job_name: 'actual-sync'
    static_configs:
      - targets: 
          - 'actual-sync-1:3000'
          - 'actual-sync-2:3000'
          - 'actual-sync-3:3000'
        labels:
          environment: 'production'
    metrics_path: '/metrics/prometheus'
```

### 3. Verify Scraping

1. Open Prometheus UI: `http://localhost:9090`
2. Go to Status → Targets
3. Verify `actual-sync` target shows "UP"
4. Test query: `actual_sync_total`

### 4. Basic Queries

Try these queries in Prometheus:

```promql
# Current success rate
actual_sync_success_rate

# Syncs in last hour
increase(actual_sync_total[1h])

# Average sync duration
rate(actual_sync_duration_seconds_sum[5m]) / rate(actual_sync_duration_seconds_count[5m])

# Failed syncs
actual_sync_total{status="error"}

# Time since last sync
time() - actual_sync_last_sync_timestamp{status="success"}
```

## Grafana Dashboard

### 1. Install Grafana

#### Docker

```bash
docker run -d \
  --name=grafana \
  -p 3001:3000 \
  -v grafana-storage:/var/lib/grafana \
  grafana/grafana
```

#### Docker Compose

Add to `docker-compose.yml`:

```yaml
services:
  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    restart: unless-stopped
    ports:
      - "3001:3000"
    volumes:
      - grafana-data:/var/lib/grafana
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=admin
      - GF_INSTALL_PLUGINS=
    networks:
      - actual-network
    depends_on:
      - prometheus

volumes:
  grafana-data:
```

### 2. Add Prometheus Data Source

1. Open Grafana: `http://localhost:3001` (admin/admin)
2. Go to Configuration → Data Sources
3. Click "Add data source"
4. Select "Prometheus"
5. Set URL: `http://prometheus:9090` (or `http://localhost:9090` if not using Docker network)
6. Click "Save & Test"

### 3. Import Dashboard

We provide a pre-built dashboard in `monitoring/grafana-dashboard.json`:

1. Go to Dashboards → Import
2. Upload `monitoring/grafana-dashboard.json`
3. Select Prometheus data source
4. Click "Import"

The dashboard includes:

- **Overview Stats**: Total syncs, success rate, accounts processed/failed
- **Sync Duration Graph**: Average duration over time with percentiles
- **Sync Rate**: Syncs per hour by status
- **Success vs Failure**: Running totals
- **Error Distribution**: Table of errors by server and code
- **Time Since Last Sync**: Staleness monitoring with alert
- **Node.js Metrics**: Memory usage and event loop lag

### 4. Custom Panels

Add custom panels for your specific needs:

#### Panel: Sync Success Rate Gauge

```json
{
  "type": "gauge",
  "targets": [{
    "expr": "actual_sync_success_rate * 100"
  }],
  "options": {
    "unit": "percent",
    "min": 0,
    "max": 100,
    "thresholds": {
      "mode": "absolute",
      "steps": [
        {"value": 0, "color": "red"},
        {"value": 90, "color": "yellow"},
        {"value": 95, "color": "green"}
      ]
    }
  }
}
```

#### Panel: Recent Errors Table

```json
{
  "type": "table",
  "targets": [{
    "expr": "topk(10, actual_sync_errors_total)",
    "format": "table",
    "instant": true
  }]
}
```

## Alerting Examples

### Prometheus Alerts

Create `alerts.yml`:

```yaml
groups:
  - name: actual-sync
    interval: 60s
    rules:
      # No sync in 2 hours
      - alert: SyncDelayed
        expr: (time() - actual_sync_last_sync_timestamp{status="success"}) > 7200
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Sync delayed for {{ $labels.server }}"
          description: "No successful sync in over 2 hours"

      # Low success rate
      - alert: LowSuccessRate
        expr: actual_sync_success_rate < 0.8
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "Low success rate for {{ $labels.server }}"
          description: "Success rate is {{ $value | humanizePercentage }}"

      # High failure count
      - alert: HighFailureCount
        expr: increase(actual_sync_total{status="error"}[1h]) > 5
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "High failure count for {{ $labels.server }}"
          description: "{{ $value }} failures in last hour"

      # Accounts failing to sync
      - alert: AccountSyncFailures
        expr: actual_sync_accounts_failed > 0
        for: 15m
        labels:
          severity: warning
        annotations:
          summary: "Account sync failures on {{ $labels.server }}"
          description: "{{ $value }} accounts failed to sync"

      # Slow sync operations
      - alert: SlowSync
        expr: rate(actual_sync_duration_seconds_sum[5m]) / rate(actual_sync_duration_seconds_count[5m]) > 60
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "Slow sync operations for {{ $labels.server }}"
          description: "Average sync duration is {{ $value }}s"

      # High memory usage
      - alert: HighMemoryUsage
        expr: process_resident_memory_bytes > 512000000
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "High memory usage"
          description: "Memory usage is {{ $value | humanize }}B"

      # Event loop lag
      - alert: HighEventLoopLag
        expr: rate(nodejs_eventloop_lag_seconds[1m]) > 1
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "High event loop lag"
          description: "Event loop lag is {{ $value }}s"
```

Add to `prometheus.yml`:

```yaml
rule_files:
  - 'alerts.yml'

alerting:
  alertmanagers:
    - static_configs:
        - targets: ['alertmanager:9093']
```

### Grafana Alerts

Configure alerts in Grafana dashboard panels:

1. Edit panel
2. Click Alert tab
3. Create alert rule
4. Configure notification channel

Example: Alert when no sync in 2 hours

- **Condition**: `WHEN last() OF query(A, 5m, now) IS ABOVE 7200`
- **Query**: `time() - actual_sync_last_sync_timestamp{status="success"}`
- **Frequency**: Every 60s
- **For**: 5m

## Troubleshooting

### Metrics Not Available

**Problem**: `/metrics/prometheus` returns 503

**Solutions**:

1. Check Prometheus service is enabled:
   ```json
   "prometheus": {"enabled": true}
   ```

2. Restart the service:
   ```bash
   npm start
   ```

3. Check logs for errors:
   ```bash
   # Check for Prometheus initialization
   grep "Prometheus metrics service" logs/*.log
   ```

### 401 Unauthorized

**Problem**: `/metrics/prometheus` returns 401 and the target shows `DOWN` with `server returned HTTP status 401 Unauthorized`

`healthCheck.auth` is set and the scrape carries no credential, or a stale one. Add the `authorization` (token) or `basic_auth` block from [the scrape config above](#2-configure-prometheus) with the current value from `config.json`, then reload Prometheus. A `429` instead of a `401` means 10 wrong attempts from the Prometheus host in the last 15 minutes: fix the credential and wait for the window to end. `/health` and `/ready` never need a credential, so a probe failing with 401 is hitting the wrong path.

### Empty Metrics

**Problem**: Endpoint works but no `actual_sync_*` metrics

**Solutions**:

1. Wait for first sync to complete (metrics recorded on sync)

2. Manually record a test metric:
   ```bash
   # This will trigger health check
   curl http://localhost:3000/health
   ```

3. Check sync history:
   ```bash
   npm run history -- --days 1
   ```

### Prometheus Not Scraping

**Problem**: Target shows "DOWN" in Prometheus

**Solutions**:

1. Verify connectivity:
   ```bash
   # From Prometheus container
   curl http://actual-sync:3000/metrics/prometheus
   ```

2. Check firewall/network:
   ```bash
   # Test port
   telnet actual-sync 3000
   ```

3. Verify metrics path in `prometheus.yml`:
   ```yaml
   metrics_path: '/metrics/prometheus'  # Correct
   ```

4. Check Prometheus logs:
   ```bash
   docker logs prometheus
   ```

### High Cardinality

**Problem**: Too many unique metric label combinations

**Solutions**:

1. Limit number of servers (each server = new label value)

2. Disable default metrics if not needed:
   ```json
   "prometheus": {
     "enabled": true,
     "includeDefaultMetrics": false
   }
   ```

3. Monitor cardinality:
   ```promql
   # Count series
   count({__name__=~"actual_sync_.*"})
   
   # Count by metric
   count by (__name__) ({__name__=~"actual_sync_.*"})
   ```

### Missing Historical Data

**Problem**: `actual_sync_success_rate` is 0 or missing

**Solutions**:

1. Ensure sync history is enabled and has data:
   ```bash
   npm run history -- --stats
   ```

2. Check sync history database:
   ```bash
   ls -lh data/sync-history.db
   ```

3. Verify `updateFromHistory()` is called:
   ```bash
   # Check logs
   grep "updated from history" logs/*.log
   ```

### Dashboard Not Loading

**Problem**: Grafana dashboard shows "No data"

**Solutions**:

1. Verify Prometheus data source connection

2. Check time range (default: last 6 hours)

3. Test query directly in Prometheus:
   ```promql
   actual_sync_total
   ```

4. Adjust dashboard queries if server labels differ

5. Check for data:
   ```promql
   # Any actual-sync metrics
   {__name__=~"actual_sync_.*"}
   ```

## Best Practices

1. **Set reasonable scrape intervals**
   - 30s for most cases
   - 10s for real-time monitoring
   - 60s for low-frequency syncs

2. **Configure retention**
   - Prometheus: 30-90 days typical
   - Sync history: Match or exceed Prometheus

3. **Use recording rules** for expensive queries:
   ```yaml
   groups:
     - name: actual-sync-recordings
       interval: 60s
       rules:
         - record: job:actual_sync_duration_seconds:avg5m
           expr: rate(actual_sync_duration_seconds_sum[5m]) / rate(actual_sync_duration_seconds_count[5m])
   ```

4. **Label consistently**
   - Keep server names short and alphanumeric
   - Avoid spaces in labels
   - Use lowercase

5. **Alert on symptoms, not causes**
   - ✅ "Sync taking too long"
   - ❌ "CPU usage high"

6. **Set up dashboards before production**
   - Test with historical data
   - Verify all panels load
   - Configure alerting

7. **Monitor metrics cardinality**
   - Track series count
   - Alert on rapid growth
   - Review label usage

## Related Documentation

- [HEALTH_CHECK.md](./HEALTH_CHECK.md) - Health check endpoint documentation
- [SYNC_HISTORY.md](./SYNC_HISTORY.md) - Sync history persistence
- [NOTIFICATIONS.md](./NOTIFICATIONS.md) - Error notification system
- [DOCKER_DEPLOYMENT.md](./DOCKER_DEPLOYMENT.md) - Docker deployment guide

## References

- [Prometheus Documentation](https://prometheus.io/docs/)
- [Grafana Documentation](https://grafana.com/docs/)
- [prom-client Library](https://github.com/siimon/prom-client)
- [PromQL Guide](https://prometheus.io/docs/prometheus/latest/querying/basics/)
- [Prometheus Best Practices](https://prometheus.io/docs/practices/naming/)