mirror of
https://github.com/nezhahq/nezha.git
synced 2025-01-22 12:48:14 -05:00
⚡️ pprof 性能调优 / 计划任务支持秒级
This commit is contained in:
parent
1f1e0b6db7
commit
47dfa4777b
3
.gitignore
vendored
3
.gitignore
vendored
@ -10,8 +10,11 @@
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
*.pprof
|
||||
.idea
|
||||
/data
|
||||
/dist
|
||||
.DS_Store
|
||||
/main
|
||||
/cmd/agent/main
|
||||
/cmd/dashboard/main
|
30
README.md
30
README.md
@ -4,7 +4,7 @@
|
||||
<br>
|
||||
<small><i>LOGO designed by <a href="https://xio.ng" target="_blank">熊大</a> .</i></small>
|
||||
<br><br>
|
||||
<img src="https://img.shields.io/github/workflow/status/naiba/nezha/Dashboard%20image?label=Dash%20v0.10.1&logo=github&style=for-the-badge"> <img src="https://img.shields.io/github/v/release/naiba/nezha?color=brightgreen&label=Agent&style=for-the-badge&logo=github"> <img src="https://img.shields.io/github/workflow/status/naiba/nezha/Agent%20release?label=Agent%20CI&logo=github&style=for-the-badge"> <img src="https://img.shields.io/badge/Installer-v0.7.0-brightgreen?style=for-the-badge&logo=linux">
|
||||
<img src="https://img.shields.io/github/workflow/status/naiba/nezha/Dashboard%20image?label=Dash%20v0.10.2&logo=github&style=for-the-badge"> <img src="https://img.shields.io/github/v/release/naiba/nezha?color=brightgreen&label=Agent&style=for-the-badge&logo=github"> <img src="https://img.shields.io/github/workflow/status/naiba/nezha/Agent%20release?label=Agent%20CI&logo=github&style=for-the-badge"> <img src="https://img.shields.io/badge/Installer-v0.7.0-brightgreen?style=for-the-badge&logo=linux">
|
||||
<br>
|
||||
<br>
|
||||
<p>:trollface: <b>哪吒监控</b> 一站式轻监控轻运维系统。支持系统状态、HTTP(SSL 证书变更、即将到期、到期)、TCP、Ping 监控报警,命令批量执行和计划任务。</p>
|
||||
@ -14,9 +14,9 @@
|
||||
|
||||
\>> [我们的用户](https://www.google.com/search?q="powered+by+哪吒监控"&filter=0) (Google)
|
||||
|
||||
| 默认主题 | DayNight [@JackieSung](https://github.com/JackieSung4ev) | hotaru |
|
||||
| ------------------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------- |
|
||||
| ![首页截图1](https://s3.ax1x.com/2020/12/07/DvTCwD.jpg) | <img src="https://s3.ax1x.com/2021/01/20/sfJv2q.jpg"/> | <img src="https://s3.ax1x.com/2020/12/09/rPF4xJ.png" width="1600px" /> |
|
||||
| 默认主题 | DayNight [@JackieSung](https://github.com/JackieSung4ev) | hotaru |
|
||||
| ----------------------------------------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------- |
|
||||
| ![默认主题](resource/template/theme-default/screenshot.png) | ![daynight](resource/template/theme-daynight/screenshot.png) | <img src="resource/template/theme-hotaru/screenshot.png" width="1600px" /> |
|
||||
|
||||
## 安装脚本
|
||||
|
||||
@ -36,11 +36,12 @@ CN=true sudo ./nezha.sh
|
||||
|
||||
_\* 使用 WatchTower 可以自动更新面板,Windows 终端可以使用 nssm 配置自启动(见尾部教程)_
|
||||
|
||||
### 特殊技能
|
||||
### 增强配置
|
||||
|
||||
通过执行 `./nezha-agent --help` 查看支持的参数,如果你使用一键脚本,可以编辑 `/etc/systemd/system/nezha-agent.service`,在 `ExecStart=` 这一行的末尾加上
|
||||
|
||||
- `--skip-conn` 不监控连接数,机场/连接密集型机器推荐设置,不然比较占 CPU([shirou/gopsutil/issues#220](https://github.com/shirou/gopsutil/issues/220))
|
||||
- `--skip-procs` 不监控进程数,也可以降低 agent 占用
|
||||
- `--disable-auto-update` 禁止 Agent 自动更新
|
||||
|
||||
## 功能说明
|
||||
@ -230,7 +231,7 @@ URL 里面也可放置占位符,请求时会进行简单的字符串替换。
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Agent 不断重启/无法启动 ?</summary>
|
||||
<summary>Agent不上线不启动自检流程</summary>
|
||||
|
||||
1. 直接执行 `/opt/nezha/agent/nezha-agent -s 面板IP或非CDN域名:面板RPC端口 -p Agent密钥 -d` 查看日志是否是 DNS 问题。
|
||||
2. `nc -v 域名/IP 面板RPC端口` 或者 `telnet 域名/IP 面板RPC端口` 检验是否是网络问题,检查本机与面板服务器出入站防火墙,如果单机无法判断可借助 <https://port.ping.pe/> 提供的端口检查工具进行检测。
|
||||
@ -243,7 +244,7 @@ URL 里面也可放置占位符,请求时会进行简单的字符串替换。
|
||||
|
||||
首先在 release 下载对应的二进制解压 tar.gz 包后放置到 `/root`,然后 `chmod +x /root/nezha-agent` 赋予执行权限,然后创建 `/etc/init.d/nezha-service`:
|
||||
|
||||
```
|
||||
```shell
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
START=99
|
||||
@ -272,8 +273,9 @@ restart() {
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>实时通道断开/终端连接失败</summary>
|
||||
使用反向代理时需要针对 `/ws` 路径的 WebSocket 进行特别配置以支持实时更新服务器状态。
|
||||
<summary>实时通道断开/网页终端连接失败</summary>
|
||||
|
||||
使用反向代理时需要针对 `/ws`,`/terminal` 路径的 WebSocket 进行特别配置以支持实时更新服务器状态和 **WebSSH**。
|
||||
|
||||
- Nginx(宝塔):在你的 nginx 配置文件中加入以下代码
|
||||
|
||||
@ -285,7 +287,6 @@ restart() {
|
||||
|
||||
location ~ ^/(ws|terminal/.+)$ {
|
||||
proxy_pass http://ip:站点访问端口;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_set_header Host $host;
|
||||
@ -295,6 +296,15 @@ restart() {
|
||||
}
|
||||
```
|
||||
|
||||
如果非宝塔,还要在 `server{}` 中添加上这一段
|
||||
|
||||
```nginx
|
||||
location / {
|
||||
proxy_pass http://ip:站点访问端口;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
```
|
||||
|
||||
- CaddyServer v1(v2 无需特别配置)
|
||||
|
||||
```Caddyfile
|
||||
|
@ -30,25 +30,21 @@ import (
|
||||
|
||||
type AgentConfig struct {
|
||||
SkipConnectionCount bool
|
||||
SkipProcsCount bool
|
||||
DisableAutoUpdate bool
|
||||
Debug bool
|
||||
Server string
|
||||
ClientSecret string
|
||||
}
|
||||
|
||||
func init() {
|
||||
http.DefaultClient.Timeout = time.Second * 5
|
||||
flag.CommandLine.ParseErrorsWhitelist.UnknownFlags = true
|
||||
}
|
||||
|
||||
var (
|
||||
version string
|
||||
agentConf AgentConfig
|
||||
version string
|
||||
client pb.NezhaServiceClient
|
||||
inited bool
|
||||
)
|
||||
|
||||
var (
|
||||
client pb.NezhaServiceClient
|
||||
inited bool
|
||||
agentConf AgentConfig
|
||||
updateCh = make(chan struct{}) // Agent 自动更新间隔
|
||||
httpClient = &http.Client{
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
@ -63,6 +59,11 @@ const (
|
||||
networkTimeOut = time.Second * 5 // 普通网络超时
|
||||
)
|
||||
|
||||
func init() {
|
||||
http.DefaultClient.Timeout = time.Second * 5
|
||||
flag.CommandLine.ParseErrorsWhitelist.UnknownFlags = true
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 来自于 GoReleaser 的版本号
|
||||
monitor.Version = version
|
||||
@ -71,6 +72,7 @@ func main() {
|
||||
flag.StringVarP(&agentConf.Server, "server", "s", "localhost:5555", "管理面板RPC端口")
|
||||
flag.StringVarP(&agentConf.ClientSecret, "password", "p", "", "Agent连接Secret")
|
||||
flag.BoolVar(&agentConf.SkipConnectionCount, "skip-conn", false, "不监控连接数")
|
||||
flag.BoolVar(&agentConf.SkipProcsCount, "skip-procs", false, "不监控进程数")
|
||||
flag.BoolVar(&agentConf.DisableAutoUpdate, "disable-auto-update", false, "禁用自动升级")
|
||||
flag.Parse()
|
||||
|
||||
@ -88,7 +90,6 @@ func run() {
|
||||
}
|
||||
|
||||
go pty.DownloadDependency()
|
||||
|
||||
// 上报服务器信息
|
||||
go reportState()
|
||||
// 更新IP信息
|
||||
@ -168,8 +169,8 @@ func receiveTasks(tasks pb.NezhaService_RequestTaskClient) error {
|
||||
}
|
||||
go func() {
|
||||
defer func() {
|
||||
if recover() != nil {
|
||||
println("task panic", task)
|
||||
if err := recover(); err != nil {
|
||||
println("task panic", task, err)
|
||||
}
|
||||
}()
|
||||
doTask(task)
|
||||
@ -209,7 +210,7 @@ func reportState() {
|
||||
if client != nil && inited {
|
||||
monitor.TrackNetworkSpeed()
|
||||
timeOutCtx, cancel := context.WithTimeout(context.Background(), networkTimeOut)
|
||||
_, err = client.ReportSystemState(timeOutCtx, monitor.GetState(agentConf.SkipConnectionCount).PB())
|
||||
_, err = client.ReportSystemState(timeOutCtx, monitor.GetState(agentConf.SkipConnectionCount, agentConf.SkipProcsCount).PB())
|
||||
cancel()
|
||||
if err != nil {
|
||||
println("reportState error", err)
|
||||
@ -229,7 +230,7 @@ func doSelfUpdate() {
|
||||
println("检查更新:", v)
|
||||
latest, err := selfupdate.UpdateSelf(v, "naiba/nezha")
|
||||
if err != nil {
|
||||
println("自动更新失败:", err)
|
||||
println("更新失败:", err)
|
||||
return
|
||||
}
|
||||
if !latest.Version.Equals(v) {
|
||||
@ -282,7 +283,7 @@ func handleHttpGetTask(task *pb.Task, result *pb.TaskResult) {
|
||||
}
|
||||
if err == nil {
|
||||
// 检查 SSL 证书信息
|
||||
if len(resp.TLS.PeerCertificates) > 0 {
|
||||
if resp.TLS != nil && len(resp.TLS.PeerCertificates) > 0 {
|
||||
c := resp.TLS.PeerCertificates[0]
|
||||
result.Data = c.Issuer.CommonName + "|" + c.NotAfter.In(time.Local).String()
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@ -15,20 +14,27 @@ import (
|
||||
"github.com/shirou/gopsutil/v3/load"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
"github.com/shirou/gopsutil/v3/net"
|
||||
"github.com/shirou/gopsutil/v3/process"
|
||||
|
||||
"github.com/naiba/nezha/model"
|
||||
)
|
||||
|
||||
var Version string = "debug"
|
||||
var netInSpeed, netOutSpeed, netInTransfer, netOutTransfer, lastUpdate uint64
|
||||
var expectDiskFsTypes = []string{
|
||||
"apfs", "ext4", "ext3", "ext2", "f2fs", "reiserfs", "jfs", "btrfs",
|
||||
"fuseblk", "zfs", "simfs", "ntfs", "fat32", "exfat", "xfs", "fuse.rclone",
|
||||
}
|
||||
var excludeNetInterfaces = []string{
|
||||
"lo", "tun", "docker", "veth", "br-", "vmbr", "vnet", "kube",
|
||||
}
|
||||
var getMacDiskNo = regexp.MustCompile(`\/dev\/disk(\d)s.*`)
|
||||
var (
|
||||
Version string = "debug"
|
||||
expectDiskFsTypes = []string{
|
||||
"apfs", "ext4", "ext3", "ext2", "f2fs", "reiserfs", "jfs", "btrfs",
|
||||
"fuseblk", "zfs", "simfs", "ntfs", "fat32", "exfat", "xfs", "fuse.rclone",
|
||||
}
|
||||
excludeNetInterfaces = []string{
|
||||
"lo", "tun", "docker", "veth", "br-", "vmbr", "vnet", "kube",
|
||||
}
|
||||
getMacDiskNo = regexp.MustCompile(`\/dev\/disk(\d)s.*`)
|
||||
)
|
||||
|
||||
var (
|
||||
netInSpeed, netOutSpeed, netInTransfer, netOutTransfer, lastUpdateNetStats uint64
|
||||
cachedBootTime time.Time
|
||||
)
|
||||
|
||||
func GetHost() *model.Host {
|
||||
hi, _ := host.Info()
|
||||
@ -58,6 +64,8 @@ func GetHost() *model.Host {
|
||||
swapMemTotal = mv.SwapTotal
|
||||
}
|
||||
|
||||
cachedBootTime = time.Now().Add(time.Duration(-1 * int64(hi.BootTime*1000)))
|
||||
|
||||
return &model.Host{
|
||||
Platform: hi.OS,
|
||||
PlatformVersion: hi.PlatformVersion,
|
||||
@ -74,8 +82,12 @@ func GetHost() *model.Host {
|
||||
}
|
||||
}
|
||||
|
||||
func GetState(skipConnectionCount bool) *model.HostState {
|
||||
hi, _ := host.Info()
|
||||
func GetState(skipConnectionCount bool, skipProcsCount bool) *model.HostState {
|
||||
var procs []int32
|
||||
if !skipProcsCount {
|
||||
procs, _ = process.Pids()
|
||||
}
|
||||
|
||||
mv, _ := mem.VirtualMemory()
|
||||
|
||||
var swapMemUsed uint64
|
||||
@ -92,11 +104,11 @@ func GetState(skipConnectionCount bool) *model.HostState {
|
||||
if err == nil {
|
||||
cpuPercent = cp[0]
|
||||
}
|
||||
|
||||
_, diskUsed := getDiskTotalAndUsed()
|
||||
loadStat, _ := load.Avg()
|
||||
|
||||
var tcpConnCount, udpConnCount uint64
|
||||
|
||||
if !skipConnectionCount {
|
||||
conns, _ := net.Connections("all")
|
||||
for i := 0; i < len(conns); i++ {
|
||||
@ -114,17 +126,17 @@ func GetState(skipConnectionCount bool) *model.HostState {
|
||||
MemUsed: mv.Total - mv.Available,
|
||||
SwapUsed: swapMemUsed,
|
||||
DiskUsed: diskUsed,
|
||||
NetInTransfer: atomic.LoadUint64(&netInTransfer),
|
||||
NetOutTransfer: atomic.LoadUint64(&netOutTransfer),
|
||||
NetInSpeed: atomic.LoadUint64(&netInSpeed),
|
||||
NetOutSpeed: atomic.LoadUint64(&netOutSpeed),
|
||||
Uptime: hi.Uptime,
|
||||
NetInTransfer: netInTransfer,
|
||||
NetOutTransfer: netOutTransfer,
|
||||
NetInSpeed: netInSpeed,
|
||||
NetOutSpeed: netOutSpeed,
|
||||
Uptime: uint64(time.Since(cachedBootTime).Seconds()),
|
||||
Load1: loadStat.Load1,
|
||||
Load5: loadStat.Load5,
|
||||
Load15: loadStat.Load15,
|
||||
TcpConnCount: tcpConnCount,
|
||||
UdpConnCount: udpConnCount,
|
||||
ProcessCount: hi.Procs,
|
||||
ProcessCount: uint64(len(procs)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,14 +152,14 @@ func TrackNetworkSpeed() {
|
||||
innerNetOutTransfer += v.BytesSent
|
||||
}
|
||||
now := uint64(time.Now().Unix())
|
||||
diff := now - atomic.LoadUint64(&lastUpdate)
|
||||
diff := now - lastUpdateNetStats
|
||||
if diff > 0 {
|
||||
atomic.StoreUint64(&netInSpeed, (innerNetInTransfer-atomic.LoadUint64(&netInTransfer))/diff)
|
||||
atomic.StoreUint64(&netOutSpeed, (innerNetOutTransfer-atomic.LoadUint64(&netOutTransfer))/diff)
|
||||
netInSpeed = (innerNetInTransfer - netInTransfer) / diff
|
||||
netOutSpeed = (innerNetOutTransfer - netOutTransfer) / diff
|
||||
}
|
||||
atomic.StoreUint64(&netInTransfer, innerNetInTransfer)
|
||||
atomic.StoreUint64(&netOutTransfer, innerNetOutTransfer)
|
||||
atomic.StoreUint64(&lastUpdate, now)
|
||||
netInTransfer = innerNetInTransfer
|
||||
netOutTransfer = innerNetOutTransfer
|
||||
lastUpdateNetStats = now
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,13 +35,21 @@ func UpdateIP() {
|
||||
for {
|
||||
ipv4 := fetchGeoIP(geoIPApiList, false)
|
||||
ipv6 := fetchGeoIP(geoIPApiList, true)
|
||||
cachedIP = fmt.Sprintf("ip(v4:%s,v6:[%s])", ipv4.IP, ipv6.IP)
|
||||
if ipv4.IP == "" && ipv6.IP == "" {
|
||||
time.Sleep(time.Minute)
|
||||
continue
|
||||
}
|
||||
if ipv4.IP == "" || ipv6.IP == "" {
|
||||
cachedIP = fmt.Sprintf("%s%s", ipv4.IP, ipv6.IP)
|
||||
} else {
|
||||
cachedIP = fmt.Sprintf("%s/%s", ipv4.IP, ipv6.IP)
|
||||
}
|
||||
if ipv4.CountryCode != "" {
|
||||
cachedCountry = ipv4.CountryCode
|
||||
} else if ipv6.CountryCode != "" {
|
||||
cachedCountry = ipv6.CountryCode
|
||||
}
|
||||
time.Sleep(time.Minute * 10)
|
||||
time.Sleep(time.Minute * 30)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,7 +112,7 @@ func ServeWeb(port uint) *http.Server {
|
||||
},
|
||||
})
|
||||
r.Static("/static", "resource/static")
|
||||
r.LoadHTMLGlob("resource/template/**/*")
|
||||
r.LoadHTMLGlob("resource/template/**/*.html")
|
||||
routers(r)
|
||||
|
||||
page404 := func(c *gin.Context) {
|
||||
|
@ -267,19 +267,22 @@ func (ma *memberAPI) addOrEditCron(c *gin.Context) {
|
||||
cr.Cover = cf.Cover
|
||||
err = json.Unmarshal([]byte(cf.ServersRaw), &cr.Servers)
|
||||
}
|
||||
if err == nil {
|
||||
_, err = cron.ParseStandard(cr.Scheduler)
|
||||
}
|
||||
tx := dao.DB.Begin()
|
||||
if err == nil {
|
||||
if cf.ID == 0 {
|
||||
err = dao.DB.Create(&cr).Error
|
||||
err = tx.Create(&cr).Error
|
||||
} else {
|
||||
err = dao.DB.Save(&cr).Error
|
||||
err = tx.Save(&cr).Error
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
cr.CronID, err = dao.Cron.AddFunc(cr.Scheduler, dao.CronTrigger(cr))
|
||||
}
|
||||
if err == nil {
|
||||
err = tx.Commit().Error
|
||||
} else {
|
||||
tx.Rollback()
|
||||
}
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, model.Response{
|
||||
Code: http.StatusBadRequest,
|
||||
|
@ -25,7 +25,7 @@ func init() {
|
||||
|
||||
// 初始化 dao 包
|
||||
dao.Conf = &model.Config{}
|
||||
dao.Cron = cron.New(cron.WithLocation(shanghai))
|
||||
dao.Cron = cron.New(cron.WithSeconds(), cron.WithLocation(shanghai))
|
||||
dao.Crons = make(map[uint64]*model.Cron)
|
||||
dao.ServerList = make(map[uint64]*model.Server)
|
||||
dao.SecretToID = make(map[string]uint64)
|
||||
@ -60,13 +60,13 @@ func initSystem() {
|
||||
loadCrons() //加载计划任务
|
||||
|
||||
// 清理 服务请求记录 和 流量记录 的旧数据
|
||||
_, err := dao.Cron.AddFunc("30 3 * * *", cleanMonitorHistory)
|
||||
_, err := dao.Cron.AddFunc("0 30 3 * * *", cleanMonitorHistory)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 流量记录打点
|
||||
_, err = dao.Cron.AddFunc("0 * * * *", recordTransferHourlyUsage)
|
||||
_, err = dao.Cron.AddFunc("0 0 * * * *", recordTransferHourlyUsage)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -162,10 +162,11 @@ func loadCrons() {
|
||||
}
|
||||
|
||||
cr.CronID, err = dao.Cron.AddFunc(cr.Scheduler, dao.CronTrigger(cr))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
if err == nil {
|
||||
dao.Crons[cr.ID] = &cr
|
||||
} else {
|
||||
log.Println("NEZHA>> 计划任务调度失败", cr, err)
|
||||
}
|
||||
dao.Crons[cr.ID] = &cr
|
||||
}
|
||||
dao.Cron.Start()
|
||||
}
|
||||
|
@ -1,7 +1,25 @@
|
||||
package main
|
||||
|
||||
import "github.com/naiba/nezha/cmd/agent/pty"
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/robfig/cron/v3"
|
||||
)
|
||||
|
||||
func main() {
|
||||
pty.DownloadDependency()
|
||||
c := cron.New(cron.WithSeconds())
|
||||
_, err := c.AddFunc("* * * * * *", func() {
|
||||
log.Println("bingo second")
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_, err = c.AddFunc("* * * * *", func() {
|
||||
log.Println("bingo minute")
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
c.Start()
|
||||
select {}
|
||||
}
|
||||
|
5
go.mod
5
go.mod
@ -7,7 +7,7 @@ require (
|
||||
github.com/artdarek/go-unzip v1.0.0
|
||||
github.com/blang/semver v3.5.1+incompatible
|
||||
github.com/creack/pty v1.1.14
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/gin-contrib/pprof v1.3.0
|
||||
github.com/gin-gonic/gin v1.7.0
|
||||
github.com/go-ping/ping v0.0.0-20210407214646-e4e642a95741
|
||||
@ -23,13 +23,14 @@ require (
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/shirou/gopsutil/v3 v3.21.7
|
||||
github.com/shirou/gopsutil/v3 v3.21.8
|
||||
github.com/spf13/pflag v1.0.3
|
||||
github.com/spf13/viper v1.7.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||
golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect
|
||||
google.golang.org/grpc v1.33.1
|
||||
google.golang.org/protobuf v1.25.0
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
|
17
go.sum
17
go.sum
@ -290,8 +290,8 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/shirou/gopsutil/v3 v3.21.7 h1:PnTqQamUjwEDSgn+nBGu0qSDV/CfvyiR/gwTH3i7HTU=
|
||||
github.com/shirou/gopsutil/v3 v3.21.7/go.mod h1:RGl11Y7XMTQPmHh8F0ayC6haKNBgH4PXMJuTAcMOlz4=
|
||||
github.com/shirou/gopsutil/v3 v3.21.8 h1:nKct+uP0TV8DjjNiHanKf8SAuub+GNsbrOtM9Nl9biA=
|
||||
github.com/shirou/gopsutil/v3 v3.21.8/go.mod h1:YWp/H8Qs5fVmf17v7JNZzA0mPJ+mS2e9JdiUF9LlKzQ=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
@ -320,10 +320,10 @@ github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tcnksm/go-gitconfig v0.1.2 h1:iiDhRitByXAEyjgBqsKi9QU4o2TNtv9kPP3RgPgXBPw=
|
||||
github.com/tcnksm/go-gitconfig v0.1.2/go.mod h1:/8EhP4H7oJZdIPyT+/UIsG87kTzrzM4UsLGSItWYCpE=
|
||||
github.com/tklauser/go-sysconf v0.3.7 h1:HT7h4+536gjqeq1ZIJPgOl1rg1XFatQGVZWp7Py53eg=
|
||||
github.com/tklauser/go-sysconf v0.3.7/go.mod h1:JZIdXh4RmBvZDBZ41ld2bGxRV3n4daiiqA3skYhAoQ4=
|
||||
github.com/tklauser/numcpus v0.2.3 h1:nQ0QYpiritP6ViFhrKYsiv6VVxOpum2Gks5GhnJbS/8=
|
||||
github.com/tklauser/numcpus v0.2.3/go.mod h1:vpEPS/JC+oZGGQ/My/vJnNsvMDQL6PwOqt8dsCw5j+E=
|
||||
github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo=
|
||||
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
|
||||
github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
|
||||
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
@ -469,8 +469,9 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -1,13 +1,11 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/spf13/viper"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
@ -68,13 +66,6 @@ func (c *Config) Read(path string) error {
|
||||
}
|
||||
|
||||
c.updateIgnoredIPNotificationID()
|
||||
|
||||
c.v.OnConfigChange(func(in fsnotify.Event) {
|
||||
c.v.Unmarshal(c)
|
||||
fmt.Println("配置文件更新,重载配置", c)
|
||||
})
|
||||
|
||||
go c.v.WatchConfig()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
2
resource/template/common/menu.html
vendored
2
resource/template/common/menu.html
vendored
@ -5,7 +5,7 @@
|
||||
<img src="/static/logo.svg?v20210804">
|
||||
</div>
|
||||
{{if .IsAdminPage}}
|
||||
<a class='item{{if eq .MatchedPath "/server"}} active{{end}}' href="/server"><i class="server icon"></i>资产</a>
|
||||
<a class='item{{if eq .MatchedPath "/server"}} active{{end}}' href="/server"><i class="server icon"></i>主机</a>
|
||||
<a class='item{{if eq .MatchedPath "/monitor"}} active{{end}}' href="/monitor"><i class="rss icon"></i>服务</a>
|
||||
<a class='item{{if eq .MatchedPath "/cron"}} active{{end}}' href="/cron"><i class="clock icon"></i>任务</a>
|
||||
<a class='item{{if eq .MatchedPath "/notification"}} active{{end}}' href="/notification"><i class="bell icon"></i>报警</a>
|
||||
|
4
resource/template/component/cron.html
vendored
4
resource/template/component/cron.html
vendored
@ -10,7 +10,7 @@
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>计划</label>
|
||||
<input type="text" name="Scheduler" placeholder="0 3 * * *(每天3点)">
|
||||
<input type="text" name="Scheduler" placeholder="0 0 3 * * *(每天3点)">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>命令</label>
|
||||
@ -41,7 +41,7 @@
|
||||
</form>
|
||||
<div class="ui warning message">
|
||||
<p>
|
||||
计划的格式为:<code>* * * * *</code> 分 时 天 月 星期,详情见 <a
|
||||
计划的格式为:<code>* * * * * *</code> 秒 分 时 天 月 星期,详情见 <a
|
||||
href="https://pkg.go.dev/github.com/robfig/cron/v3#hdr-CRON_Expression_Format"
|
||||
target="_blank">计划表达式格式</a><br>
|
||||
命令:就像写 shell/bat 脚本一样,但是不推荐换行,多个命令使用 <code>&&</code>/<code>&</code> 连接,如果遇到 xxx 命令找不到,可能是
|
||||
|
@ -5,7 +5,7 @@
|
||||
<div class="ui grid">
|
||||
<div class="right floated right aligned twelve wide column">
|
||||
<button class="ui right labeled nezha-primary-btn icon button" onclick="addOrEditMonitor()">
|
||||
<i class="add icon"></i> 添加监控
|
||||
<i class="add icon"></i> 添加服务
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -43,7 +43,7 @@
|
||||
<i class="edit icon"></i>
|
||||
</button>
|
||||
<button class="ui button"
|
||||
onclick="showConfirm('删除监控','确认删除此监控?',deleteRequest,'/api/monitor/'+{{$monitor.ID}})">
|
||||
onclick="showConfirm('删除服务','确认删除此服务?',deleteRequest,'/api/monitor/'+{{$monitor.ID}})">
|
||||
<i class="trash alternate outline icon"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<div class="ui grid">
|
||||
<div class="right floated right aligned twelve wide column">
|
||||
<button class="ui right labeled nezha-primary-btn icon button" onclick="addOrEditServer()"><i
|
||||
class="add icon"></i> 添加服务器
|
||||
class="add icon"></i> 添加主机
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -56,7 +56,7 @@
|
||||
<i class="edit icon"></i>
|
||||
</button>
|
||||
<button class="ui button"
|
||||
onclick="showConfirm('删除节点','确认删除此监控节点?',deleteRequest,'/api/server/'+{{$server.ID}})">
|
||||
onclick="showConfirm('删除主机','确认删除此主机?',deleteRequest,'/api/server/'+{{$server.ID}})">
|
||||
<i class="trash alternate outline icon"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
BIN
resource/template/theme-daynight/screenshot.png
vendored
Normal file
BIN
resource/template/theme-daynight/screenshot.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 481 KiB |
1
resource/template/theme-default/README.md
vendored
1
resource/template/theme-default/README.md
vendored
@ -1 +0,0 @@
|
||||
# 默认主题
|
BIN
resource/template/theme-default/screenshot.png
vendored
Normal file
BIN
resource/template/theme-default/screenshot.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 856 KiB |
BIN
resource/template/theme-hotaru/screenshot.png
vendored
Normal file
BIN
resource/template/theme-hotaru/screenshot.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.0 MiB |
@ -13,7 +13,7 @@ import (
|
||||
pb "github.com/naiba/nezha/proto"
|
||||
)
|
||||
|
||||
var Version = "v0.10.1" // !!记得修改 README 中的 badge 版本!!
|
||||
var Version = "v0.10.2" // !!记得修改 README 中的 badge 版本!!
|
||||
|
||||
var (
|
||||
Conf *model.Config
|
||||
|
@ -10,7 +10,6 @@ import (
|
||||
|
||||
"github.com/naiba/nezha/model"
|
||||
pb "github.com/naiba/nezha/proto"
|
||||
"github.com/robfig/cron/v3"
|
||||
)
|
||||
|
||||
const _CurrentStatusSize = 30 // 统计 15 分钟内的数据为当前状态
|
||||
@ -42,11 +41,9 @@ func NewServiceSentinel(serviceSentinelDispatchBus chan<- model.Monitor) {
|
||||
sslCertCache: make(map[uint64]string),
|
||||
// 30天数据缓存
|
||||
monthlyStatus: make(map[uint64]*model.ServiceItemResponse),
|
||||
dispatchCron: cron.New(cron.WithSeconds()),
|
||||
dispatchBus: serviceSentinelDispatchBus,
|
||||
}
|
||||
ServiceSentinelShared.loadMonitorHistory()
|
||||
ServiceSentinelShared.dispatchCron.Start()
|
||||
|
||||
year, month, day := time.Now().Date()
|
||||
today := time.Date(year, month, day, 0, 0, 0, 0, time.Local)
|
||||
@ -75,7 +72,7 @@ func NewServiceSentinel(serviceSentinelDispatchBus chan<- model.Monitor) {
|
||||
go ServiceSentinelShared.worker()
|
||||
|
||||
// 每日将游标往后推一天
|
||||
_, err := Cron.AddFunc("0 0 * * *", ServiceSentinelShared.refreshMonthlyServiceStatus)
|
||||
_, err := Cron.AddFunc("0 0 0 * * *", ServiceSentinelShared.refreshMonthlyServiceStatus)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -102,8 +99,7 @@ type ServiceSentinel struct {
|
||||
monthlyStatusLock sync.Mutex
|
||||
monthlyStatus map[uint64]*model.ServiceItemResponse
|
||||
// 服务监控调度计划任务
|
||||
dispatchCron *cron.Cron
|
||||
dispatchBus chan<- model.Monitor
|
||||
dispatchBus chan<- model.Monitor
|
||||
}
|
||||
|
||||
func (ss *ServiceSentinel) refreshMonthlyServiceStatus() {
|
||||
@ -147,7 +143,7 @@ func (ss *ServiceSentinel) loadMonitorHistory() {
|
||||
ss.monitors = make(map[uint64]*model.Monitor)
|
||||
for i := 0; i < len(monitors); i++ {
|
||||
task := *monitors[i]
|
||||
monitors[i].CronJobID, err = ss.dispatchCron.AddFunc(task.CronSpec(), func() {
|
||||
monitors[i].CronJobID, err = Cron.AddFunc(task.CronSpec(), func() {
|
||||
ss.dispatchBus <- task
|
||||
})
|
||||
log.Println("NEZHA>> 服务监控任务", monitors[i].ID, monitors[i].Name, monitors[i].CronJobID)
|
||||
@ -194,7 +190,7 @@ func (ss *ServiceSentinel) OnMonitorUpdate(m model.Monitor) error {
|
||||
defer ss.monitorsLock.Unlock()
|
||||
var err error
|
||||
// 写入新任务
|
||||
m.CronJobID, err = ss.dispatchCron.AddFunc(m.CronSpec(), func() {
|
||||
m.CronJobID, err = Cron.AddFunc(m.CronSpec(), func() {
|
||||
ss.dispatchBus <- m
|
||||
})
|
||||
if err != nil {
|
||||
@ -202,7 +198,7 @@ func (ss *ServiceSentinel) OnMonitorUpdate(m model.Monitor) error {
|
||||
}
|
||||
if ss.monitors[m.ID] != nil {
|
||||
// 停掉旧任务
|
||||
ss.dispatchCron.Remove(ss.monitors[m.ID].CronJobID)
|
||||
Cron.Remove(ss.monitors[m.ID].CronJobID)
|
||||
} else {
|
||||
// 新任务初始化数据
|
||||
ss.monthlyStatusLock.Lock()
|
||||
@ -236,7 +232,7 @@ func (ss *ServiceSentinel) OnMonitorDelete(id uint64) {
|
||||
ss.monitorsLock.Lock()
|
||||
defer ss.monitorsLock.Unlock()
|
||||
// 停掉定时任务
|
||||
ss.dispatchCron.Remove(ss.monitors[id].CronJobID)
|
||||
Cron.Remove(ss.monitors[id].CronJobID)
|
||||
delete(ss.monitors, id)
|
||||
ss.monthlyStatusLock.Lock()
|
||||
defer ss.monthlyStatusLock.Unlock()
|
||||
|
Loading…
Reference in New Issue
Block a user