package agent import ( "fmt" "strconv" "strings" "github.com/pulse-lets-go/internal/ami" ) // AsteriskCollector собирает метрики с Asterisk через AMI. type AsteriskCollector struct { client *ami.Client } // NewAsteriskCollector создаёт Asterisk-коллектор. func NewAsteriskCollector(host string, port int, username, password string) *AsteriskCollector { return &AsteriskCollector{ client: ami.NewClient(host, port, username, password), } } func (ac *AsteriskCollector) Type() string { return "asterisk" } func (ac *AsteriskCollector) Connect() error { return ac.client.Connect() } func (ac *AsteriskCollector) Collect() (*PBXResult, error) { if !ac.client.IsConnected() { return nil, fmt.Errorf("ami не подключён") } result := &PBXResult{Status: "ok"} // active_calls: CoreShowChannels определяет количество активных каналов if active, err := ac.coreShowChannelsCount(); err == nil { result.ActiveCalls = active } // max_calls: CoreSettings → CoreMaxCalls if max, err := ac.coreSettingsInt("CoreMaxCalls"); err == nil { result.MaxCalls = max } // idle_cpu: Asterisk не даёт FS-шного idle_cpu, используем системный result.IdleCPU = 0 return result, nil } func (ac *AsteriskCollector) ListenHangups(onHangup func(success bool)) { // Подписываемся на события Hangup через AMI ac.client.Subscribe("call") go func() { for evt := range ac.client.Events() { if evt.Headers["Event"] == "Hangup" { cause := evt.Headers["Cause"] // Считаем успешным только NORMAL_CLEARING (код 16) success := cause == "16" || strings.Contains(cause, "NORMAL_CLEARING") onHangup(success) } } }() } func (ac *AsteriskCollector) Close() { ac.client.Disconnect() } // --- helpers --- // coreShowChannelsCount получает количество активных каналов. func (ac *AsteriskCollector) coreShowChannelsCount() (int, error) { cmd := "Action: Command\r\nCommand: core show channels count\r\n" headers, body, err := ac.client.SendCommandBody(cmd) if err != nil { return 0, err } _ = headers // Ответ выглядит как: "1 active call" или "5 active calls" body = strings.TrimSpace(body) fields := strings.Fields(body) if len(fields) >= 3 { // Первое слово — число n, err := strconv.Atoi(fields[0]) if err == nil { return n, nil } } return 0, fmt.Errorf("не удалось распарсить количество каналов из: %s", body) } // coreSettingsInt получает целочисленное значение из CoreSettings. func (ac *AsteriskCollector) coreSettingsInt(key string) (int, error) { headers, _, err := ac.client.SendCommandBody("Action: CoreSettings\r\n") if err != nil { return 0, err } val := headers[key] if val == "" { return 0, fmt.Errorf("поле %s не найдено", key) } return strconv.Atoi(strings.TrimSpace(val)) }