欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

基于Go語言開發(fā)篇一個(gè)命令行進(jìn)程監(jiān)控工具

 更新時(shí)間:2025年09月15日 08:18:42   作者:程序員愛釣魚  
在生產(chǎn)和開發(fā)環(huán)境中,監(jiān)控關(guān)鍵進(jìn)程的存活與資源使用是非常常見的需求,本篇將開發(fā)一個(gè)輕量級(jí)的命令行進(jìn)程監(jiān)控工具,感興趣的小伙伴可以了解下

在生產(chǎn)和開發(fā)環(huán)境中,監(jiān)控關(guān)鍵進(jìn)程的存活與資源使用是非常常見的需求:當(dāng)進(jìn)程 CPU/內(nèi)存超限或意外退出時(shí)自動(dòng)告警、記錄歷史、甚至重啟進(jìn)程,能顯著提升系統(tǒng)可靠性。本篇給出一個(gè)可運(yùn)行的 Go 實(shí)戰(zhàn)案例:一個(gè)輕量級(jí)的命令行進(jìn)程監(jiān)控工具(procmon),支持按進(jìn)程名或 PID 監(jiān)控、采樣統(tǒng)計(jì)、閾值告警(HTTP webhook)、并能執(zhí)行重啟命令。

下面從目標(biāo)、設(shè)計(jì)、實(shí)現(xiàn)到運(yùn)行示例一步步展開,并給出可以直接拿去編譯運(yùn)行的完整代碼。

功能目標(biāo)

  • 監(jiān)控指定的進(jìn)程(按名稱或 PID),周期性采樣 CPU% 與內(nèi)存 RSS。
  • 當(dāng)某個(gè)進(jìn)程 CPU% 或內(nèi)存(MB)超出閾值時(shí)觸發(fā)告警(支持 HTTP webhook + 本地日志)。
  • 支持在告警時(shí)運(yùn)行自定義重啟命令(可用于 systemd restart、docker restart、或自定義腳本)。
  • 支持本地日志、控制臺(tái)輸出、并優(yōu)雅退出(SIGINT/SIGTERM)。
  • 支持批量監(jiān)控多個(gè)進(jìn)程、簡單配置(命令行 flags / JSON)。

技術(shù)選型

  • 語言:Go
  • 進(jìn)程信息:github.com/shirou/gopsutil/v3/process(跨平臺(tái),常用)
  • 告警:HTTP POST 到 webhook(簡單可擴(kuò)展到郵件/釘釘/Slack)
  • 并發(fā):每個(gè)監(jiān)控項(xiàng)使用獨(dú)立 goroutine,主循環(huán)統(tǒng)一調(diào)度與統(tǒng)計(jì)

項(xiàng)目結(jié)構(gòu)(示意)

procmon/
├── main.go
├── go.mod

完整代碼(main.go)

// main.go
package main

import (
	"bytes"
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/exec"
	"os/signal"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/shirou/gopsutil/v3/process"
)

// MonitorConfig 表示對(duì)單個(gè)進(jìn)程的監(jiān)控配置
type MonitorConfig struct {
	Names       []string `json:"names"`        // 按進(jìn)程名匹配
	PIDs        []int32  `json:"pids"`         // 指定 pid
	CPUThreshold float64 `json:"cpu_threshold"` // 百分比,如 80.0
	MemThreshold float64 `json:"mem_threshold"` // MB,如 500.0
	RestartCmd   string  `json:"restart_cmd"`   // 告警時(shí)執(zhí)行的重啟命令(可空)
}

// AlertPayload 告警時(shí)發(fā)送的 JSON 結(jié)構(gòu)
type AlertPayload struct {
	Time      time.Time `json:"time"`
	Host      string    `json:"host"`
	Process   string    `json:"process"`
	PID       int32     `json:"pid"`
	CPU       float64   `json:"cpu_percent"`
	MemoryMB  float64   `json:"memory_mb"`
	Triggered string    `json:"triggered"`
	Msg       string    `json:"msg"`
}

func main() {
	// CLI 參數(shù)
	cfgFile := flag.String("config", "", "配置 JSON 文件(可選),與命令行參數(shù)組合使用")
	names := flag.String("names", "", "要監(jiān)控的進(jìn)程名,逗號(hào)分隔(例如: nginx,mysqld)")
	pids := flag.String("pids", "", "要監(jiān)控的 pid,逗號(hào)分隔(例如: 123,456)")
	interval := flag.Duration("interval", 5*time.Second, "采樣間隔")
	cpuTh := flag.Float64("cpu", 80.0, "默認(rèn) CPU 百分比閾值(%)")
	memTh := flag.Float64("mem", 500.0, "默認(rèn) 內(nèi)存閾值(MB)")
	webhook := flag.String("webhook", "", "告警 webhook URL(POST 接收 JSON)")
	restart := flag.String("restart", "", "全局重啟命令(可選,覆蓋 config 中 restart_cmd)")
	flag.Parse()

	// 解析配置
	var monitors []MonitorConfig
	if *cfgFile != "" {
		f, err := os.ReadFile(*cfgFile)
		if err != nil {
			log.Fatalf("讀取配置文件失敗: %v", err)
		}
		if err := json.Unmarshal(f, &monitors); err != nil {
			log.Fatalf("解析配置文件失敗: %v", err)
		}
	}

	// 命令行 args 補(bǔ)充單一配置(如果用戶沒傳配置文件)
	if len(monitors) == 0 && (*names != "" || *pids != "") {
		m := MonitorConfig{
			CPUThreshold: *cpuTh,
			MemThreshold: *memTh,
		}
		if *names != "" {
			for _, n := range strings.Split(*names, ",") {
				n = strings.TrimSpace(n)
				if n != "" {
					m.Names = append(m.Names, n)
				}
			}
		}
		if *pids != "" {
			for _, ps := range strings.Split(*pids, ",") {
				if s := strings.TrimSpace(ps); s != "" {
					id, err := strconv.Atoi(s)
					if err == nil {
						m.PIDs = append(m.PIDs, int32(id))
					}
				}
			}
		}
		if *restart != "" {
			m.RestartCmd = *restart
		}
		monitors = append(monitors, m)
	}

	if len(monitors) == 0 {
		log.Fatalln("沒有任何監(jiān)控配置。請(qǐng)通過 -config 或 -names/-pids 提供配置。")
	}

	hostname, _ := os.Hostname()
	ctx, cancel := context.WithCancel(context.Background())
	wg := &sync.WaitGroup{}

	// 信號(hào)優(yōu)雅退出
	sigc := make(chan os.Signal, 1)
	signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM)
	go func() {
		<-sigc
		log.Println("收到退出信號(hào),正在優(yōu)雅停止...")
		cancel()
	}()

	// 啟動(dòng)每個(gè)監(jiān)控項(xiàng)的 goroutine
	for idx, mc := range monitors {
		wg.Add(1)
		go func(id int, cfg MonitorConfig) {
			defer wg.Done()
			monitorLoop(ctx, id, cfg, *interval, *webhook, hostname)
		}(idx, mc)
	}

	// 等待退出
	wg.Wait()
	log.Println("procmon 已退出")
}

// monitorLoop 對(duì)單個(gè) MonitorConfig 進(jìn)行輪詢監(jiān)控
func monitorLoop(ctx context.Context, id int, cfg MonitorConfig, interval time.Duration, webhook, host string) {
	logPrefix := fmt.Sprintf("[monitor-%d] ", id)
	logger := log.New(os.Stdout, logPrefix, log.LstdFlags)

	ticker := time.NewTicker(interval)
	defer ticker.Stop()

	// 用于去重告警(避免短時(shí)間內(nèi)頻繁告警)
	alerted := make(map[int32]time.Time)
	alertCooldown := 30 * time.Second // 同一 pid 告警最小間隔

	for {
		select {
		case <-ctx.Done():
			logger.Println("停止監(jiān)控(context canceled)")
			return
		case <-ticker.C:
			procs, err := process.Processes()
			if err != nil {
				logger.Printf("獲取進(jìn)程列表失敗: %v\n", err)
				continue
			}
			now := time.Now()
			for _, p := range procs {
				match := false
				// 匹配 PID 列表
				for _, pid := range cfg.PIDs {
					if p.Pid == pid {
						match = true
						break
					}
				}
				// 匹配名字列表(如果未通過 pid 匹配)
				if !match && len(cfg.Names) > 0 {
					name, err := p.Name()
					if err == nil {
						for _, nm := range cfg.Names {
							if strings.EqualFold(name, nm) {
								match = true
								break
							}
						}
					}
				}
				if !match {
					continue
				}

				// 獲取 CPU & Mem
				// Percent 需要傳入一個(gè)間隔來計(jì)算;這里使用 0 來獲取自上次調(diào)用以后的值(某些平臺(tái))
				// 更可靠的做法是調(diào)用 Percent(interval);為了簡單與跨平臺(tái),這里使用 Percent(0)
				cpuPercent, errCpu := p.CPUPercent()
				memInfo, errMem := p.MemoryInfo()
				if errCpu != nil || errMem != nil || memInfo == nil {
					// 有時(shí)權(quán)限原因無法讀取某些信息
					logger.Printf("讀取進(jìn)程 %d 信息失敗: cpuErr=%v memErr=%v\n", p.Pid, errCpu, errMem)
					continue
				}
				memMB := float64(memInfo.RSS) / 1024.0 / 1024.0

				// 打印日志
				name, _ := p.Name()
				logger.Printf("進(jìn)程 %s pid=%d cpu=%.2f%% mem=%.2fMB\n", name, p.Pid, cpuPercent, memMB)

				// 判斷閾值
				triggered := ""
				if cfg.CPUThreshold > 0 && cpuPercent >= cfg.CPUThreshold {
					triggered = "cpu"
				}
				if cfg.MemThreshold > 0 && memMB >= cfg.MemThreshold {
					if triggered == "" {
						triggered = "mem"
					} else {
						triggered = "cpu+mem"
					}
				}
				if triggered != "" {
					lastAlert, ok := alerted[p.Pid]
					if ok && now.Sub(lastAlert) < alertCooldown {
						// 跳過頻繁告警
						logger.Printf("已在 cooldown 中,跳過 pid=%d 的告警\n", p.Pid)
						continue
					}
					alerted[p.Pid] = now

					payload := AlertPayload{
						Time:      now,
						Host:      host,
						Process:   name,
						PID:       p.Pid,
						CPU:       cpuPercent,
						MemoryMB:  memMB,
						Triggered: triggered,
						Msg:       fmt.Sprintf("process %s (pid=%d) exceeded threshold (%s)", name, p.Pid, triggered),
					}
					// 本地日志告警
					logger.Printf("ALERT: %s\n", payload.Msg)

					// 發(fā)送 webhook(如果配置)
					if webhook != "" {
						go func(pl AlertPayload) {
							if err := postAlert(webhook, pl); err != nil {
								logger.Printf("發(fā)送 webhook 失敗: %v\n", err)
							} else {
								logger.Printf("告警已發(fā)送到 %s\n", webhook)
							}
						}(payload)
					}

					// 執(zhí)行重啟命令(config 中或全局傳入) —— 先嘗試 graceful terminate 再執(zhí)行重啟命令(如果提供)
					if cfg.RestartCmd != "" {
						go func(cmdStr string, targetPid int32) {
							logger.Printf("嘗試殺掉 pid=%d 并執(zhí)行重啟命令: %s\n", targetPid, cmdStr)
							// 發(fā)送 TERM
							_ = p.SendSignal(syscall.SIGTERM)
							// 等待短時(shí)間讓進(jìn)程退出
							time.Sleep(2 * time.Second)
							// 強(qiáng)制 kill 如果還存在
							exists, _ := process.PidExists(targetPid)
							if exists {
								_ = p.Kill()
							}
							// 執(zhí)行重啟命令(通過 shell)
							cmd := exec.Command("/bin/sh", "-c", cmdStr)
							out, err := cmd.CombinedOutput()
							if err != nil {
								logger.Printf("執(zhí)行重啟命令失敗: %v. output: %s\n", err, string(out))
							} else {
								logger.Printf("重啟命令已執(zhí)行, output: %s\n", string(out))
							}
						}(cfg.RestartCmd, p.Pid)
					}
				}
			} // end for procs
		} // end ticker select
	} // end for
}

// postAlert 以 JSON POST 方式發(fā)送告警
func postAlert(webhook string, payload AlertPayload) error {
	bs, _ := json.Marshal(payload)
	req, err := http.NewRequest("POST", webhook, bytes.NewReader(bs))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")
	client := &http.Client{Timeout: 8 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return fmt.Errorf("webhook 返回非 2xx: %s", resp.Status)
	}
	return nil
}

代碼說明 & 要點(diǎn)提醒

依賴:本示例使用 github.com/shirou/gopsutil/v3/process。在項(xiàng)目目錄運(yùn)行:

go mod init procmon
go get github.com/shirou/gopsutil/v3/process

采樣 CPUprocess.CPUPercent() 的行為受平臺(tái)和調(diào)用頻率影響。更精確的 CPU 百分比通常需要兩次采樣間的時(shí)間差(gopsutil 提供相關(guān)接口),但本例為簡潔使用了庫的默認(rèn)方法。若需精確長期統(tǒng)計(jì),可以保存上次樣本并計(jì)算 delta。

權(quán)限問題:在某些系統(tǒng)上讀取其他用戶的進(jìn)程信息需要更高權(quán)限(root)。如果監(jiān)控不到目標(biāo)進(jìn)程,請(qǐng)以合適權(quán)限運(yùn)行。

重啟策略:示例中通過 RestartCmd 執(zhí)行自定義 shell 命令來重啟服務(wù)(例如 systemctl restart myservicedocker restart container)。這是最靈活的方式,但要確保命令安全(不要盲目執(zhí)行來自不可信配置的命令)。

告警去重:示例里使用 alertCooldown 防止短時(shí)間內(nèi)重復(fù)告警。你可以把告警狀態(tài)持久化到 Redis/文件以跨重啟保留告警狀態(tài)。

跨平臺(tái):gopsutil 支持多平臺(tái),但信號(hào)、kill 等行為在 Windows 與 Unix 上不同。Windows 上需用不同方法停止進(jìn)程。

使用示例

簡單按進(jìn)程名監(jiān)控 nginx,CPU 超過 70% 或內(nèi)存超過 300MB 時(shí)發(fā) webhook:

./procmon -names nginx -cpu 70 -mem 300 -webhook "https://example.com/webhook"

使用 JSON 配置(config.json)支持多項(xiàng)監(jiān)控(文件示例):

[
  {
    "names": ["nginx"],
    "cpu_threshold": 70.0,
    "mem_threshold": 300,
    "restart_cmd": "systemctl restart nginx"
  },
  {
    "names": ["mysqld"],
    "cpu_threshold": 85.0,
    "mem_threshold": 2048,
    "restart_cmd": "systemctl restart mysql"
  }
]

運(yùn)行:

./procmon -names nginx -cpu 70 -mem 300 -webhook "https://example.com/webhook"

可行的擴(kuò)展與改進(jìn)(工程化建議)

  • 持久化歷史:把采樣結(jié)果寫入 InfluxDB/Prometheus 或本地文件,方便后續(xù)分析與告警策略優(yōu)化。
  • 更智能的告警:支持平均值/移動(dòng)窗口、抑制波動(dòng)(例如短時(shí) spike 不告警)、按時(shí)間段不同閾值。
  • 進(jìn)程自恢復(fù):把重啟策略從單條命令擴(kuò)展為“逐步恢復(fù)”:先重啟、再報(bào)警、再回滾;并記錄重啟次數(shù)以避免重啟風(fēng)暴。
  • UI 或 API:提供 HTTP 管理接口查看當(dāng)前監(jiān)控狀態(tài)、觸發(fā)測試告警或調(diào)整閾值。
  • 容器/Pod 支持:在容器環(huán)境下識(shí)別容器內(nèi)進(jìn)程或直接對(duì)容器做重啟(Kubernetes 中可使用 K8s API 觸發(fā)重啟)。
  • 權(quán)限和安全:限制能夠執(zhí)行的 restart_cmd、對(duì) webhook 使用簽名/鑒權(quán)避免被濫用。

小結(jié)

本文實(shí)現(xiàn)了一個(gè)簡單但實(shí)用的 Go 進(jìn)程監(jiān)控工具,涵蓋進(jìn)程掃描、資源采樣、閾值檢測、告警與重啟動(dòng)作。示例代碼足夠作為生產(chǎn)工具的原型,通過增加持久化、更多告警通道與更安全的重啟策略,可以逐步把它演化為完整的運(yùn)維監(jiān)控組件。

到此這篇關(guān)于基于Go語言開發(fā)篇一個(gè)命令行進(jìn)程監(jiān)控工具的文章就介紹到這了,更多相關(guān)Go進(jìn)程監(jiān)控內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解Go語言的錯(cuò)誤處理和資源管理

    詳解Go語言的錯(cuò)誤處理和資源管理

    資源處理是什么?打開文件需要關(guān)閉,打開數(shù)據(jù)庫連接,連接需要釋放。這些成對(duì)出現(xiàn)的就是資源管理。有時(shí)候我們雖然釋放了,但是程序在中間出錯(cuò)了,那么可能導(dǎo)致資源釋放失敗。如何保證打開的文件一定會(huì)被關(guān)閉呢?這就是資源管理與錯(cuò)誤處理考慮的一個(gè)原因
    2021-06-06
  • Golang語言學(xué)習(xí)拿捏Go反射示例教程

    Golang語言學(xué)習(xí)拿捏Go反射示例教程

    這篇文章主要為大家介紹了Golang語言中Go反射示例的教程,教你拿捏Go反射,再也不用被Go反射折磨,有需要的朋友可以共同學(xué)習(xí)參考下
    2021-11-11
  • 解讀 Go 中的 constraints包完整案例

    解讀 Go 中的 constraints包完整案例

    Go1.18引入constraints包,提供泛型類型約束接口(如Signed、Unsigned、Ordered、Comparable),用于限制類型參數(shù)并提升類型安全與代碼復(fù)用,內(nèi)置any和comparable約束,當(dāng)前處于實(shí)驗(yàn)階段,本文給大家介紹解讀 Go 中的 constraints包,感興趣的朋友一起看看吧
    2025-07-07
  • Go語言WaitGroup使用時(shí)需要注意的坑

    Go語言WaitGroup使用時(shí)需要注意的坑

    Go語言中WaitGroup的用途是它能夠一直等到所有的goroutine執(zhí)行完成,并且阻塞主線程的執(zhí)行,直到所有的goroutine執(zhí)行完成。之前一直使用也沒有問題,但最近通過同事的一段代碼引起了關(guān)于WaitGroup的注意,下面這篇文章就介紹了WaitGroup使用時(shí)需要注意的坑及填坑。
    2016-12-12
  • go語言基礎(chǔ)語法示例

    go語言基礎(chǔ)語法示例

    這篇文章主要介紹了go語言基礎(chǔ)語法示例,介紹了go語言較為全面的基礎(chǔ)知識(shí),具有一定參考價(jià)值,需要的可以了解下。
    2017-11-11
  • go語言中函數(shù)與方法介紹

    go語言中函數(shù)與方法介紹

    這篇文章介紹了go語言中的函數(shù)與方法,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-07-07
  • GoLang基礎(chǔ)學(xué)習(xí)之go?test測試

    GoLang基礎(chǔ)學(xué)習(xí)之go?test測試

    相信每位編程開發(fā)者們應(yīng)該都知道,Golang作為一門標(biāo)榜工程化的語言,提供了非常簡便、實(shí)用的編寫單元測試的能力,下面這篇文章主要給大家介紹了關(guān)于GoLang基礎(chǔ)學(xué)習(xí)之go?test測試的相關(guān)資料,需要的朋友可以參考下
    2022-08-08
  • Golang實(shí)現(xiàn)按比例切分流量的示例詳解

    Golang實(shí)現(xiàn)按比例切分流量的示例詳解

    我們?cè)谶M(jìn)行灰度發(fā)布時(shí),往往需要轉(zhuǎn)發(fā)一部分流量到新上線的服務(wù)上,進(jìn)行小規(guī)模的驗(yàn)證,隨著功能的不斷完善,我們也會(huì)逐漸增加轉(zhuǎn)發(fā)的流量,這就需要按比例去切分流量,那么如何實(shí)現(xiàn)流量切分呢,接下來小編就給大家詳細(xì)的介紹一下實(shí)現(xiàn)方法,需要的朋友可以參考下
    2023-09-09
  • 使用Go語言玩轉(zhuǎn) RESTful API 服務(wù)

    使用Go語言玩轉(zhuǎn) RESTful API 服務(wù)

    RESTful API是一種基于HTTP協(xié)議的API設(shè)計(jì)風(fēng)格,遵循REST架構(gòu)風(fēng)格,這篇文章主要為大家介紹了如何通過Go語言構(gòu)建RESTful API服務(wù),有需要的可以了解下
    2025-02-02
  • 一文帶你搞懂Golang如何正確退出Goroutine

    一文帶你搞懂Golang如何正確退出Goroutine

    在Go語言中,Goroutine是一種輕量級(jí)線程,它的退出機(jī)制對(duì)于并發(fā)編程至關(guān)重要,下午就來介紹幾種Goroutine的退出機(jī)制,希望對(duì)大家有所幫助
    2023-06-06

最新評(píng)論