6.0 KiB

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

{
  "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

make build-agent
# → bin/pulse-lets-go-agent (static binary, ~9 MB)

Configuration (agent.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)

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

# 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

# /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

# 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