- Create docs/ with 7 focused files: architecture, api, scoring, agent, deployment, freeswitch, development - Rewrite README.md as concise overview with links to docs/ - Remove AGENTS.md (content merged into docs/ and README)
179 lines
6.1 KiB
Markdown
179 lines
6.1 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": "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_<type>.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` |
|