MENU

【Golang】服务器进程监控器

2025 年 12 月 01 日 •

引子

大模型的训练和推理是很耗时的,如果没有及时知晓训练/推理完成的时刻,则可能会造成时间上的损失。比如睡前明明可以再跑一个模型,结果认为上一个模型没跑完,就浪费了一晚上的时间。代码开源在https://github.com/coder109/Email-After-Process-Done

所以,我做了这样一个工具,监控进程运行的开始、结束时刻,结束了就把信息发给我的邮箱。大概的设计是这样的:记录时间-运行进程-记录时间-发送邮件。

主函数

主函数也是按照设计来的:

func main() {
    // Init
    var info pkg.Information
    pkg.GetTime(&info, "start")
    email_info := pkg.InitPrivateEmailInformation()

    // Get the Command Name
    command := pkg.BuildCommand(os.Args)
    pkg.GetCommandName(&info)

    // Find the Path
    path := pkg.LookPath(command)

    // Start the Process
    pro, err := os.StartProcess(path, command, &os.ProcAttr{})
    if err != nil {
        panic(err)
    }

    // Wait
    pro.Wait()

    pkg.GetTime(&info, "end")
    content := pkg.InformationToString(info)
    fmt.Println(content)
    pkg.SendEmail(content, email_info.Sender, email_info.Recver, email_info.Port, email_info.Smtp_server, email_info.Password)
}
  1. 维护一个信息,便于最后发送邮件。
  2. 随后获取当前的时间,也就是进程开始运行的时间。
  3. 记录命令,并且将命令也记录到信息中。
  4. 记录可执行程序的地址,便于StartProcess函数使用。
  5. 如果程序完美无缺地执行了,就等待程序运行结束,记录结束时间并发送邮件。
  6. 否则,就报错。

这里存在着可以优化的一点,就是如果出错了也发邮件,但是,这一个更新我想放在后面,将运行结果、日志或者错误作为附件。

command的维护

command的维护主要集中在代码中的executor.go文件中,其主要内容如下:

func BuildCommand(args []string) []string {
    if len(args) == 1 {
        panic("No Command")
    }

    command := append([]string{}, args[1:]...)
    return command
}

func LookPath(command_name []string) string {
    path, err := exec.LookPath(command_name[0])
    if err != nil {
        panic(err)
    }
    return path
}

这里的内容很简单,没什么可说的。

邮件发送

邮件发送的代码在代码的sender.go中,主要应用了外部的email库。

信息维护

信息维护主要集中在代码的timelogger.go中,代码如下:

type Information struct {
    command_name []string
    time_start   [6]string
    time_end     [6]string
}

func Month2Num(month string) int {
    return map[string]int{
        "January":   1,
        "February":  2,
        "March":     3,
        "April":     4,
        "May":       5,
        "June":      6,
        "July":      7,
        "August":    8,
        "September": 9,
        "October":   10,
        "November":  11,
        "December":  12,
    }[month]
}

func GetCommandName(information *Information) {
    information.command_name = os.Args[1:]
}

func GetTime(information *Information, flag string) {
    now := time.Now()
    year, raw_month, day := now.Date()
    hour, minute, second := now.Clock()

    month := Month2Num(raw_month.String())

    if flag == "start" {
        information.time_start = [6]string{fmt.Sprint(year), fmt.Sprint(month), fmt.Sprint(day), fmt.Sprint(hour), fmt.Sprint(minute), fmt.Sprint(second)}
    } else if flag == "end" {
        information.time_end = [6]string{fmt.Sprint(year), fmt.Sprint(month), fmt.Sprint(day), fmt.Sprint(hour), fmt.Sprint(minute), fmt.Sprint(second)}
    } else {
        panic("Invalid Flag")
    }
}

func InformationToString(information Information) string {
    return fmt.Sprintf(
        "The command name is %v\nThe start time is %s-%s-%s %s:%s:%s\nThe end time is %s-%s-%s %s:%s:%s\n",
        information.command_name,
        information.time_start[0], information.time_start[1], information.time_start[2], information.time_start[3], information.time_start[4], information.time_start[5],
        information.time_end[0], information.time_end[1], information.time_end[2], information.time_end[3], information.time_end[4], information.time_end[5],
    )
}

主要就是对时间的处理,以及最后的信息输出。

总结

这个代码很简单,但是也能体现出golang相比于C的优势——表达力更强,可应用的库多且易写。