# Metrics Agent (pulse-lets-go-agent) The agent runs on each PBX node (FreeSWITCH or Asterisk), collects metrics, and publishes them to NATS every 5 seconds. ## Architecture ``` ┌─────────────────────┐ pulse.metrics. ┌─────────────────────┐ │ pulse-lets-go-agent│──────────────────────────▶ │ NATS Server │ │ (on each PBX node) │ every 5 seconds │ (central) │ ├─────────────────────┤ └────────┬────────────┘ │ FreeSWITCH (ESL) │ │ │ Asterisk (AMI) │ ▼ │ System (/proc) │ ┌─────────────────┐ └─────────────────────┘ │ pulse-lets-go │ │ (scoring engine) │ └─────────────────┘ ``` ## NATS Message Format ```json { "node_id": "pbx-01", "ts": 1719000000, "status": "ok", "active_calls": 42, "max_calls": 250, "idle_cpu": 35, "load_avg": 0.85, "call_failure_rate": 0.5, "sip_gateway": "sip:pbx-01.lan:5060" } ``` - `status`: `"ok"` or any error string (triggers lethal) - `call_failure_rate`: 0.0..100.0 (percentage) - `sip_gateway`: optional, triggers auto-trunk creation on first metric ## Installation ```bash make build-agent # → bin/pulse-lets-go-agent (static binary, ~9 MB) ``` ## Configuration (agent.json) ```json { "node_id": "uc06", "type": "asterisk", "nats_url": "nats://balancer.host:4222", "nats_user": "", "nats_password": "", "interval_sec": 5, "max_calls": 150, "sip_gateway": "sip:uc-pbx.lan:5060", "sip_gateway_auto": false, "failure_window": 1000, "esl": { "host": "127.0.0.1", "port": 8021, "password": "ClueCon" }, "ami": { "host": "127.0.0.1", "port": 6154, "username": "admin", "password": "changeme" } } ``` | Field | Type | Default | Description | |-------|------|---------|-------------| | `node_id` | string | — | Unique node ID (uc06, ses-sip) | | `type` | string | — | `freeswitch` or `asterisk` | | `nats_url` | string | — | NATS server URL | | `nats_user` | string | `""` | NATS username | | `nats_password` | string | `""` | NATS password | | `interval_sec` | int | `5` | Metric publish interval | | `max_calls` | int | `250` | Node capacity (fallback) | | `sip_gateway` | string | `""` | SIP address for auto-trunk creation | | `sip_gateway_auto` | bool | `false` | Auto-detect SIP from PBX | | `failure_window` | int | `1000` | Window size for call_failure_rate | | `esl.*` | object | — | FreeSWITCH ESL settings (`host`, `port`, `password`) | | `ami.*` | object | — | Asterisk AMI settings (`host`, `port`, `username`, `password`) | ## Collectors | Collector | Source | Metrics | PBX | |-----------|--------|---------|-----| | `freeswitch` | ESL: `show channels count`, `json status`, `eval $${idle_cpu}` | active_calls, max_calls, idle_cpu | ✅ | | `asterisk` | AMI: `CoreShowChannels`, `CoreSettings`, Hangup events | active_calls, max_calls, call_failure_rate | ✅ | | `system` | `/proc/loadavg`, `/proc/stat` | load_avg, idle_cpu (fallback) | ✅ | ### Call Failure Rate Sliding window of last N (default 1000) completed calls. | Outcome | FS (ESL) | Asterisk (AMI) | |---------|----------|---------------| | Success | `CHANNEL_HANGUP` + `Hangup-Cause: NORMAL_CLEARING` | `Hangup` + `Cause: 16` | | Failure | Any other `Hangup-Cause` | Any other `Cause` | ## Collector Interface (extensibility) ```go type Collector interface { Type() string // "freeswitch" | "asterisk" Connect() error // ESL auth / AMI login Collect() (*PBXResult, error) // Collect metrics ListenHangup(onHangup func(bool)) // Subscribe to hangup events Close() // Close connection } ``` New PBX type = new `collector_.go` implementing 5 interface methods. No existing code changes needed. ## Template Configs | File | Target | Type | |------|--------|------| | `contrib/agent-uc.json` | Asterisk UC nodes (05, 06, 66-69) | asterisk | | `contrib/agent-sessip.json` | FreeSWITCH ses-sip (ses-pbx.lan) | freeswitch | ## Deployment ```bash # 1. Build make build-agent # 2. Copy to node scp bin/pulse-lets-go-agent admin@NODE_IP:/usr/local/bin/ # 3. Create config scp contrib/agent-uc.json admin@NODE_IP:/etc/pulse-lets-go-agent/agent.json # 4. Start ssh admin@NODE_IP "pulse-lets-go-agent -config /etc/pulse-lets-go-agent/agent.json" ``` ## Systemd Unit ```ini # /etc/systemd/system/pulse-lets-go-agent.service [Unit] Description=Pulse Lets Go Agent — PBX metrics collector After=network.target Wants=network-online.target [Service] Type=simple User=root ExecStart=/usr/local/bin/pulse-lets-go-agent -config /etc/pulse-lets-go-agent/agent.json Restart=always RestartSec=5 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target ``` ## Verification ```bash # On PBX node — check agent log: journalctl -u pulse-lets-go-agent -f # Expected: # [publisher] NATS connected to nats://host:4222 # [ami] connected to 127.0.0.1:6154 (for Asterisk) # [agent] published metric: calls=0/150 load=0.28 cpu=99% status=ok # On balancer — check node appeared: curl http://localhost:8080/api/nodes | jq '.[] | {node_id, score, active_calls}' ``` ## Troubleshooting | Problem | Cause | Solution | |---------|-------|----------| | `nats: no servers available` | NATS not running or unreachable | `systemctl start nats-server` | | `ami auth rejected` | Wrong password or access | Check `/etc/asterisk/manager.conf` | | `esl connect timeout` | ESL not listening on port | `fs_cli -x "load mod_event_socket"` | | `calls=0/0` | max_calls not obtained | Set `max_calls` in `agent.json` |