使用Go编写一个进程监控软件
不怕服务器挂点,自动重启
使用Go编写一个进程监控软件
有这样一个需求:服务器上需要开启多个服务器程序,并且需要统计每一个程序的运行情况,如果某一个程序出现崩溃,需要重新启动该程序。 从需求上看:
- 查看进程中运行了哪些程序,并把已运行中的程序进行记录。
- 如果设定的程序没有开启,则重新开启程序。
- 监控所有的程序运行状态,并记录程序的cpu占用和内存占用。
获取进程
安装进程库文件
go get “github.com/shirou/gopsutil/v3/process”
processes, err := process.Processes()
if err != nil {
fmt.Println(err.Error())
return
}
for _, proc := range processes {
//遍历所有的进程
}
根据端口判断程序是否正常运行
获取端口的方法可以使用系统自带的netstat
命令
linux:
statnet -tunlp
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.1:43879 0.0.0.0:* LISTEN 1227/node
tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:33060 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:6379 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN -
tcp6 0 0 ::1:6379 :::* LISTEN -
tcp6 0 0 :::17758 :::* LISTEN 2429/./linker
tcp6 0 0 :::17759 :::* LISTEN 2429/./linker
windows:
statnet -aon
协议 本地地址 外部地址 状态 PID
TCP 0.0.0.0:135 0.0.0.0:0 LISTENING 1364
TCP 0.0.0.0:445 0.0.0.0:0 LISTENING 4
TCP 0.0.0.0:2179 0.0.0.0:0 LISTENING 2268
TCP 0.0.0.0:5040 0.0.0.0:0 LISTENING 6192
TCP 0.0.0.0:7680 0.0.0.0:0 LISTENING 18016
解析数据分析出端口和pid
//按行解析数据,将tcp或者TCP开头的数据进行解析
// 移除字符串中多余的空格
func MoveMoreSpace(origin string) string {
buf := []byte(origin)
writer := bytes.NewBuffer([]byte{})
space_index := -1
get_letter := false
for i := 0; i < len(buf); i++ {
if buf[i] == ' ' {
if space_index == -1 && get_letter {
writer.WriteByte(buf[i])
space_index = i
}
} else {
get_letter = true
if space_index != -1 {
space_index = -1
}
writer.WriteByte(buf[i])
}
}
return writer.String()
}
//获取系统版本
func GetOS() {
env := os.Getenv("OS")
if strings.HasPrefix(env, "Windows") {
fmt.Println("Windows environment")
w.netArgs = []string{"-aon"}
w.is_linux = false
} else {
fmt.Println("unix environment netstat -tunlp")
w.netArgs = []string{"-tunlp"}
w.is_linux = true
}
}
//检测所有监控的程序
func (w *Watcher) CheckNetListen() {
cmd := exec.Command("netstat", w.netArgs...)
out, err := cmd.StdoutPipe()
if err != nil {
panic(err.Error())
}
err = cmd.Start()
if err != nil {
fmt.Println(err.Error())
}
s := bufio.NewScanner(out)
for s.Scan() {
head_str := string(s.Bytes())
head_str = utils.MoveMoreSpace(head_str)
splits := strings.Split(head_str, " ")
if splits[0] == "TCP" || splits[0] == "tcp" {
if w.is_linux {
lastIndex := strings.LastIndex(splits[3], ":")
if lastIndex != -1 {
port, err := strconv.Atoi(splits[3][lastIndex+1:])
if err != nil {
log.Println(err.Error())
}
if _, ok := w.MapExec[port]; ok {
pid_str := strings.Split(splits[len(splits)-1], "/")[0]
pid, err := strconv.Atoi(pid_str)
if err != nil {
log.Println(err.Error())
}
w.CheckExitsPid[port] = pid
delete(w.MapExec, port)
}
}
} else {
ip_splits := strings.Split(splits[1], ":")
if len(ip_splits) == 2 {
port, err := strconv.Atoi(ip_splits[1])
if err != nil {
log.Fatal(err.Error())
}
if _, ok := w.MapExec[port]; ok {
pid, err := strconv.Atoi(splits[len(splits)-1])
if err != nil {
log.Println(err.Error())
}
w.CheckExitsPid[port] = pid
delete(w.MapExec, port)
}
}
}
}
cmd.Wait()
}
启动服务程序
//进程文件
type (
ProcessData struct {
Exec string //程序目录
Args []string //参数
Port int //端口
}
)
func (p *ProcStarter) startProcess(info utils.ProcessData) {
// 创建日志文件
_, err := os.Stat("log")
if err != nil {
fmt.Println(err.Error())
os.Mkdir("log", os.ModePerm)
} else {
fmt.Println("exists")
}
//检测尚未启动的进程,并且启动他
if _, ok := p.MapPortProc[info.Port]; !ok {
name := strings.Split(info.Exec, ".")[0]
idx := strings.LastIndex(info.Exec, "/")
if idx != -1 {
name = strings.Split(info.Exec[idx+1:], ".")[0]
}
out_f, err := os.OpenFile(fmt.Sprintf("log/%s_%d_%s-out.log", name, info.Port, time.Now().Format("20060102150405")), os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
fmt.Println(err.Error())
}
err_f, err := os.OpenFile(fmt.Sprintf("log/%s_%d_%s-error.log", name, info.Port, time.Now().Format("20060102150405")), os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
fmt.Println(err.Error())
}
procAttr := &os.ProcAttr{
Dir: "E:\\go\\src\\kirin_go_combine\\game\\pochiko", //执行目录, 如果Exec传递的是绝对路径,这里可以不用传递
Env: p.env,
Sys: &syscall.SysProcAttr{
HideWindow: false,
},
Files: []*os.File{
nil,
out_f,
err_f,
},
}
proc, err := os.StartProcess(info.Exec, []string{info.Exec, "-port", strconv.Itoa(info.Port)}, procAttr)
if err != nil {
fmt.Printf("Error %v starting process!", err) //
}
checkMap := make(map[int]int)
checkMap[info.Port] = int(proc.Pid)
p.CheckExistsProcess(checkMap)
}
}
检测程序是否已经挂了(监控程序的cpu和内存占用)
RSS(Resident set size)实际使用物理内存(包含共享库占用的内存) 如果占用内存过低,可以认为此进程已经挂了
func (p *ProcStarter) MonitorRes(check chan struct{}) {
ticker := time.NewTicker(time.Second * 30)
for {
<-ticker.C
rm := make([]int, 0)
for port, v := range p.MapPortProc {
merinfo, err := v.MemoryInfo()
if err != nil {
fmt.Println(err.Error())
rm = append(rm, port)
continue
}
if merinfo.RSS < 100_0000 { //进程应该挂了
rm = append(rm, port)
continue
}
cpu, err := v.CPUPercent()
if err != nil {
fmt.Println(err.Error())
rm = append(rm, port)
continue
}
memer, err := v.MemoryPercent()
if err != nil {
fmt.Println(err.Error())
rm = append(rm, port)
continue
}
fmt.Println(port, cpu, memer, merinfo)
}
if len(rm) > 0 {
for _, port := range rm {
delete(p.MapPortProc, port)
}
check <- struct{}{}
}
}
}
GitHub地址: https://github.com/lixxix/go_monitor
最后修改于 2023-08-25