179 lines
6.0 KiB
Markdown

# 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.<id> ┌─────────────────────┐
│ 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": "pbx-01",
"type": "asterisk",
"nats_url": "nats://balancer.host:4222",
"nats_user": "",
"nats_password": "",
"interval_sec": 5,
"max_calls": 150,
"sip_gateway": "sip:pbx-01.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 (pbx-01, pbx-02) |
| `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_<type>.go` implementing 5 interface methods. No existing code changes needed.
## Template Configs
| File | Target | Type |
|------|--------|------|
| `contrib/agent-uc.json` | Asterisk UC nodes | asterisk |
| `contrib/agent-sessip.json` | FreeSWITCH ses-sip | freeswitch |
## Deployment
```bash
# 1. Build
make build-agent
# 2. Copy to node
scp bin/pulse-lets-go-agent admin@pbx-node:/usr/local/bin/
# 3. Create config
scp contrib/agent-uc.json admin@pbx-node:/etc/pulse-lets-go-agent/agent.json
# 4. Start
ssh admin@pbx-node "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` |