Building a Next‑Gen AIOps Monitoring System with Go and DeepSeek LLM
This article walks through constructing a high‑performance server‑monitoring probe in Go, integrating DeepSeek's large language model for AI‑driven anomaly analysis, and demonstrates end‑to‑end setup, metric collection, alert throttling, compilation, and stress‑testing on Ubuntu.
Environment Setup and System Initialization
Update Ubuntu LTS (20.04‑24.04) and install required tools:
sudo apt update && sudo apt upgrade -y
sudo apt install -y wget curl git build-essentialDownload and install Go 1.23.6:
# Set version
GO_VERSION="1.23.6"
# Download binary
wget https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz
# Extract to /usr/local
sudo tar -C /usr/local -xzf go${GO_VERSION}.linux-amd64.tar.gz
# Clean up
rm go${GO_VERSION}.linux-amd64.tar.gzConfigure the Go environment:
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
echo 'export GOPATH=$HOME/go' >> ~/.bashrc
echo 'export PATH=$PATH:$GOPATH/bin' >> ~/.bashrc
source ~/.bashrc
go versionDeepSeek Model Integration
Register on the Lanyun platform to obtain an API Key. The DeepSeek‑V3.2 model is accessed via:
Base URL: https://maas-api.lanyun.net/v1/chat/completions
Model ID: /maas/deepseek-ai/DeepSeek-V3.2System Architecture and Go Implementation
The project server-monitor is a Go module (minimum Go 1.21):
module server-monitor
go 1.21Configuration – a Config struct separates thresholds and AI credentials from monitoring logic:
type Config struct {
CPUThreshold float64
MemThreshold float64
DiskThreshold float64
Interval int // seconds
AlertCooldown int // seconds
AIBaseURL string
AIAPIKey string
AIModel string
}Metric Collection reads Linux /proc pseudo‑files: /proc/stat – CPU usage calculated from two snapshots 500 ms apart. /proc/meminfo – uses MemAvailable for realistic free memory. syscall.Statfs – disk usage. /proc/net/dev – network byte counters (non‑loopback interfaces).
Key functions:
func readCPUStat() (*cpuStat, error) { /* parse /proc/stat */ }
func collectCPU(m *Metrics) error { /* two samples, diff calculation */ }
func collectMemory(m *Metrics) error { /* parse /proc/meminfo */ }
func collectDisk(m *Metrics) error { /* syscall.Statfs('/') */ }
func collectNetwork(m *Metrics) error { /* parse /proc/net/dev */ }AI Analysis is triggered when anomalies are detected. The request payload includes formatted metrics and a list of anomalies:
prompt := fmt.Sprintf(`Server metrics at %s:
- CPU Usage: %.1f%%
- Memory Usage: %.1f%% (%.1f GB / %.1f GB)
- Disk Usage: %.1f%% (%.1f GB / %.1f GB)
- Network: Sent %d MB, Received %d MB
Detected anomalies:
%s
Please analyze these anomalies and provide recommendations.`, ...)
reqBody := chatRequest{Model: cfg.AIModel, Messages: []chatMessage{{Role:"system",Content:"You are a server monitoring expert. Analyze the provided metrics and anomalies, then give a brief summary and 2-3 actionable recommendations."},{Role:"user",Content:prompt}}}The response is unmarshalled and printed.
Alert Suppression uses an Alerter that records the last alert time for each anomaly fingerprint. If the cooldown (default 300 s) has not elapsed, the alert is skipped:
func (a *Alerter) check(m *Metrics) {
var anomalies []string
if m.CPUPercent > a.cfg.CPUThreshold { anomalies = append(anomalies, fmt.Sprintf("CPU %.1f%% > threshold %.1f%%", m.CPUPercent, a.cfg.CPUThreshold)) }
// similar checks for memory and disk
if len(anomalies) == 0 { return }
key := anomaliesKey(anomalies)
if last, ok := a.lastAlert[key]; ok && time.Since(last) < time.Duration(a.cfg.AlertCooldown)*time.Second { return }
a.lastAlert[key] = time.Now()
// print alert and call AI
}Main Loop creates a time.Ticker based on the configured interval (default 30 s) and repeatedly collects metrics, prints a concise summary, and invokes the alert checker:
func runMonitor(cfg *Config) {
alerter := newAlerter(cfg)
fmt.Printf("Server Monitor started (interval: %ds | CPU>%.0f%% Mem>%.0f%% Disk>%.0f%%)
", cfg.Interval, cfg.CPUThreshold, cfg.MemThreshold, cfg.DiskThreshold)
tick := func() {
m, err := collectMetrics()
if err != nil { fmt.Printf("[ERROR] %v
", err); return }
fmt.Printf("[%s] %s
", m.Timestamp.Format("15:04:05"), m.String())
alerter.check(m)
}
tick()
ticker := time.NewTicker(time.Duration(cfg.Interval) * time.Second)
defer ticker.Stop()
for range ticker.C { tick() }
}Compilation and Stress‑Testing
Build the statically linked binary: go build -o main main.go && ./main Validate alert logic with stress-ng (or a simple yes loop). Example: stress-ng --cpu 4 --timeout 60s or for i in 1 2 3 4; do yes > /dev/null & done During the load, the console prints an [ALERT] line, followed by "Calling AI for analysis..." and the AI‑generated diagnostic report, which includes actionable suggestions such as checking runaway processes or upgrading hardware.
Key Code Overview (main.go)
package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
"syscall"
"time"
)
// ========== Config ==========
type Config struct {
CPUThreshold float64
MemThreshold float64
DiskThreshold float64
Interval int // seconds
AlertCooldown int // seconds
AIBaseURL string
AIAPIKey string
AIModel string
}
func loadConfig() *Config {
return &Config{
CPUThreshold: 5.0, // test value, adjust for production
MemThreshold: 25.0, // test value
DiskThreshold: 90.0,
Interval: 30,
AlertCooldown: 300,
AIBaseURL: "https://maas-api.lanyun.net/v1/chat/completions",
AIAPIKey: "xxxxxxxxxxx",
AIModel: "/maas/deepseek-ai/DeepSeek-V3.2",
}
}
// ========== Metrics ==========
type Metrics struct {
Timestamp time.Time
CPUPercent float64
MemoryPercent float64
MemoryUsedGB float64
MemoryTotalGB float64
DiskPercent float64
DiskUsedGB float64
DiskTotalGB float64
NetBytesSent uint64
NetBytesRecv uint64
}
func (m *Metrics) String() string {
return fmt.Sprintf("CPU: %.1f%% | Memory: %.1f%% (%.1fGB/%.1fGB) | Disk: %.1f%% (%.1fGB/%.1fGB) | Net: sent=%dMB recv=%dMB",
m.CPUPercent,
m.MemoryPercent, m.MemoryUsedGB, m.MemoryTotalGB,
m.DiskPercent, m.DiskUsedGB, m.DiskTotalGB,
m.NetBytesSent/1024/1024, m.NetBytesRecv/1024/1024)
}
func collectMetrics() (*Metrics, error) {
m := &Metrics{Timestamp: time.Now()}
if err := collectCPU(m); err != nil { return nil, fmt.Errorf("cpu: %w", err) }
if err := collectMemory(m); err != nil { return nil, fmt.Errorf("memory: %w", err) }
if err := collectDisk(m); err != nil { return nil, fmt.Errorf("disk: %w", err) }
if err := collectNetwork(m); err != nil { return nil, fmt.Errorf("network: %w", err) }
return m, nil
}
// ========== Collectors ==========
type cpuStat struct { user, nice, system, idle, iowait, irq, softirq uint64 }
func readCPUStat() (*cpuStat, error) {
f, err := os.Open("/proc/stat")
if err != nil { return nil, err }
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
if !strings.HasPrefix(line, "cpu ") { continue }
fields := strings.Fields(line)
if len(fields) < 8 { return nil, fmt.Errorf("unexpected /proc/stat format") }
parse := func(i int) uint64 { v, _ := strconv.ParseUint(fields[i], 10, 64); return v }
return &cpuStat{user: parse(1), nice: parse(2), system: parse(3), idle: parse(4), iowait: parse(5), irq: parse(6), softirq: parse(7)}, nil
}
return nil, fmt.Errorf("cpu line not found in /proc/stat")
}
func collectCPU(m *Metrics) error {
s1, err := readCPUStat()
if err != nil { return err }
time.Sleep(500 * time.Millisecond)
s2, err := readCPUStat()
if err != nil { return err }
idle1 := s1.idle + s1.iowait
idle2 := s2.idle + s2.iowait
total1 := s1.user + s1.nice + s1.system + s1.idle + s1.iowait + s1.irq + s1.softirq
total2 := s2.user + s2.nice + s2.system + s2.idle + s2.iowait + s2.irq + s2.softirq
totalDiff := float64(total2 - total1)
idleDiff := float64(idle2 - idle1)
if totalDiff == 0 {
m.CPUPercent = 0
} else {
m.CPUPercent = (1.0 - idleDiff/totalDiff) * 100.0
}
return nil
}
func collectMemory(m *Metrics) error {
f, err := os.Open("/proc/meminfo")
if err != nil { return err }
defer f.Close()
vals := make(map[string]uint64)
scanner := bufio.NewScanner(f)
for scanner.Scan() {
fields := strings.Fields(scanner.Text())
if len(fields) >= 2 {
key := strings.TrimSuffix(fields[0], ":")
v, _ := strconv.ParseUint(fields[1], 10, 64)
vals[key] = v
}
}
total := vals["MemTotal"]
available := vals["MemAvailable"]
if total == 0 { return fmt.Errorf("MemTotal not found") }
used := total - available
m.MemoryTotalGB = float64(total) / 1024 / 1024
m.MemoryUsedGB = float64(used) / 1024 / 1024
m.MemoryPercent = float64(used) / float64(total) * 100.0
return nil
}
func collectDisk(m *Metrics) error {
var stat syscall.Statfs_t
if err := syscall.Statfs("/", &stat); err != nil { return err }
total := stat.Blocks * uint64(stat.Bsize)
free := stat.Bfree * uint64(stat.Bsize)
used := total - free
m.DiskTotalGB = float64(total) / 1024 / 1024 / 1024
m.DiskUsedGB = float64(used) / 1024 / 1024 / 1024
if total > 0 {
m.DiskPercent = float64(used) / float64(total) * 100.0
}
return nil
}
func collectNetwork(m *Metrics) error {
f, err := os.Open("/proc/net/dev")
if err != nil { return err }
defer f.Close()
var totalSent, totalRecv uint64
scanner := bufio.NewScanner(f)
scanner.Scan() // header 1
scanner.Scan() // header 2
for scanner.Scan() {
line := scanner.Text()
colonIdx := strings.Index(line, ":")
if colonIdx < 0 { continue }
iface := strings.TrimSpace(line[:colonIdx])
if iface == "lo" { continue }
fields := strings.Fields(line[colonIdx+1:])
if len(fields) < 9 { continue }
recv, _ := strconv.ParseUint(fields[0], 10, 64)
sent, _ := strconv.ParseUint(fields[8], 10, 64)
totalRecv += recv
totalSent += sent
}
m.NetBytesSent = totalSent
m.NetBytesRecv = totalRecv
return nil
}
// ========== AI Analyzer ==========
type chatMessage struct { Role string `json:"role"`; Content string `json:"content"` }
type chatRequest struct { Model string `json:"model"`; Messages []chatMessage `json:"messages"` }
type chatChoice struct { Message chatMessage `json:"message"` }
type chatResponse struct { Choices []chatChoice `json:"choices"` }
func analyzeWithAI(cfg *Config, m *Metrics, anomalies []string) (string, error) {
anomalyList := ""
for _, a := range anomalies { anomalyList += "- " + a + "
" }
prompt := fmt.Sprintf(`Server metrics at %s:
- CPU Usage: %.1f%%
- Memory Usage: %.1f%% (%.1f GB / %.1f GB)
- Disk Usage: %.1f%% (%.1f GB / %.1f GB)
- Network: Sent %d MB, Received %d MB
Detected anomalies:
%s
Please analyze these anomalies and provide recommendations.`,
m.Timestamp.Format("2006-01-02 15:04:05"), m.CPUPercent, m.MemoryPercent, m.MemoryUsedGB, m.MemoryTotalGB,
m.DiskPercent, m.DiskUsedGB, m.DiskTotalGB, m.NetBytesSent/1024/1024, m.NetBytesRecv/1024/1024, anomalyList)
reqBody := chatRequest{Model: cfg.AIModel, Messages: []chatMessage{{Role:"system", Content:"You are a server monitoring expert. Analyze the provided metrics and anomalies, then give a brief summary and 2-3 actionable recommendations."}, {Role:"user", Content:prompt}}}
data, err := json.Marshal(reqBody)
if err != nil { return "", err }
client := &http.Client{Timeout: 30 * time.Second}
req, err := http.NewRequest("POST", cfg.AIBaseURL, bytes.NewReader(data))
if err != nil { return "", err }
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+cfg.AIAPIKey)
resp, err := client.Do(req)
if err != nil { return "", fmt.Errorf("API request failed: %w", err) }
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil { return "", err }
if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("API error %d: %s", resp.StatusCode, string(body)) }
var chatResp chatResponse
if err := json.Unmarshal(body, &chatResp); err != nil { return "", fmt.Errorf("parse response: %w", err) }
if len(chatResp.Choices) == 0 { return "", fmt.Errorf("empty response from AI") }
return chatResp.Choices[0].Message.Content, nil
}
// ========== Alerter ==========
type Alerter struct { cfg *Config; lastAlert map[string]time.Time }
func newAlerter(cfg *Config) *Alerter { return &Alerter{cfg: cfg, lastAlert: make(map[string]time.Time)} }
func (a *Alerter) check(m *Metrics) {
var anomalies []string
if m.CPUPercent > a.cfg.CPUThreshold { anomalies = append(anomalies, fmt.Sprintf("CPU %.1f%% > threshold %.1f%%", m.CPUPercent, a.cfg.CPUThreshold)) }
if m.MemoryPercent > a.cfg.MemThreshold { anomalies = append(anomalies, fmt.Sprintf("Memory %.1f%% > threshold %.1f%%", m.MemoryPercent, a.cfg.MemThreshold)) }
if m.DiskPercent > a.cfg.DiskThreshold { anomalies = append(anomalies, fmt.Sprintf("Disk %.1f%% > threshold %.1f%%", m.DiskPercent, a.cfg.DiskThreshold)) }
if len(anomalies) == 0 { return }
key := strings.Join(anomalies, "|")[:min(len(strings.Join(anomalies, "|")), 40)]
if last, ok := a.lastAlert[key]; ok && time.Since(last) < time.Duration(a.cfg.AlertCooldown)*time.Second { return }
a.lastAlert[key] = time.Now()
fmt.Println("
==================================================")
fmt.Printf("[ALERT] %s
", time.Now().Format("2006-01-02 15:04:05"))
fmt.Println("Anomalies detected:")
for _, an := range anomalies { fmt.Printf(" ! %s
", an) }
fmt.Println("
Calling AI for analysis...")
analysis, err := analyzeWithAI(a.cfg, m, anomalies)
if err != nil { fmt.Printf("AI analysis failed: %v
", err) } else { fmt.Println("
--- AI Analysis ---"); fmt.Println(analysis) }
fmt.Println("==================================================")
}
func min(a, b int) int { if a < b { return a }; return b }
// ========== Monitor ==========
func runMonitor(cfg *Config) {
alerter := newAlerter(cfg)
fmt.Printf("Server Monitor started (interval: %ds | CPU>%.0f%% Mem>%.0f%% Disk>%.0f%%)
", cfg.Interval, cfg.CPUThreshold, cfg.MemThreshold, cfg.DiskThreshold)
tick := func() {
m, err := collectMetrics()
if err != nil { fmt.Printf("[ERROR] %v
", err); return }
fmt.Printf("[%s] %s
", m.Timestamp.Format("15:04:05"), m.String())
alerter.check(m)
}
tick()
ticker := time.NewTicker(time.Duration(cfg.Interval) * time.Second)
defer ticker.Stop()
for range ticker.C { tick() }
}
func main() {
cfg := loadConfig()
runMonitor(cfg)
}Stress‑Test Validation
Run the binary and generate CPU load with stress-ng or the yes loop. The monitor prints alerts when the CPU usage exceeds the configured threshold, then displays the AI‑generated diagnostic report, which includes concrete recommendations (e.g., investigate runaway processes or consider hardware upgrades).
Conclusion
The guide demonstrates a complete pipeline: Linux metric extraction via /proc, Go‑based data processing, configurable alert suppression, and AI‑driven anomaly interpretation using the DeepSeek‑V3.2 model. The prototype can be extended with time‑series storage (Prometheus), visualization (Grafana), or gRPC‑based distributed monitoring to form an enterprise‑grade AIOps platform.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Go Development Architecture Practice
Daily sharing of Golang-related technical articles, practical resources, language news, tutorials, real-world projects, and more. Looking forward to growing together. Let's go!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
