This commit is contained in:
LollipopKit 2023-03-17 18:18:50 +08:00
parent a229097af3
commit 0301db3813
8 changed files with 150 additions and 122 deletions

16
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "auto",
"program": ".",
"args": ["serve"]
}
]
}

View File

@ -9,10 +9,10 @@ import (
)
type Push struct {
Type PushType `json:"type"`
Type PushType `json:"type"`
Iface json.RawMessage `json:"iface"`
TitleFormat PushFormat `json:"title"`
ContentFormat PushFormat `json:"content"`
TitleFormat PushFormat `json:"title"`
ContentFormat PushFormat `json:"content"`
}
func (p *Push) GetIface() (PushIface, error) {
@ -45,14 +45,6 @@ func (p *Push) Push(args []*PushFormatArgs) error {
return iface.push(title, content)
}
func (p *Push) Id() string {
// switch p.PushType {
// case PushTypeIOS:
// return "iOS-" + p.PushIface.(*PushIOS).Token[:7]
// case PushTypeWebhook:
// return "Webhook-" + p.PushIface.(*PushWebhook).Url
// default:
// return "UnknownPushId"
// }
iface, err := p.GetIface()
if err != nil {
return "UnknownPushIface"

View File

@ -5,15 +5,11 @@ import (
"fmt"
"strconv"
"strings"
"github.com/lollipopkit/server_box_monitor/utils"
)
var (
ErrInvalidRule = errors.New("invalid rule")
ErrInvalidMonitorType = errors.New("invalid monitor type")
ErrInvalidThresholdType = errors.New("invalid threshold type")
ErrCompareFailed = errors.New("compare failed")
)
type Rule struct {
@ -34,9 +30,8 @@ type Rule struct {
// MonitorType = "temp" && Matcher = "x86_pkg" -> temperature of x86_pkg
Matcher string `json:"matcher"`
}
func (r *Rule) Id() string {
return fmt.Sprintf("%s-%s-%s", r.MonitorType, r.Threshold, r.Matcher)
return fmt.Sprintf("[%s %s %s]", r.MonitorType, r.Threshold, r.Matcher)
}
func (r *Rule) ShouldNotify(s *Status) (bool, *PushFormatArgs, error) {
t, err := ParseToThreshold(r.Threshold)
@ -75,27 +70,31 @@ func (r *Rule) shouldNotifyCPU(ss []CPUStatus, t *Threshold) (bool, *PushFormatA
}
idx = int64(idx_ + 1)
}
if idx < 0 || int(idx) >= len(ss) {
return false, nil, errors.Join(ErrInvalidRule, fmt.Errorf("cpu index out of range: %d", idx))
}
s := ss[idx]
switch t.ThresholdType {
case ThresholdTypePercent:
ok, err := t.True(s.UsedPercent)
percent, err := s.UsedPercent()
if err != nil {
return false, nil, errors.Join(ErrInvalidRule, err)
return false, nil, err
}
ok, err := t.True(percent)
if err != nil {
return false, nil, err
}
usedPercent, err := s.UsedPercent()
if err != nil {
return false, nil, errors.Join(ErrInvalidRule, err)
return false, nil, err
}
return ok, &PushFormatArgs{
Key: fmt.Sprintf("cpu%d", idx),
Value: fmt.Sprintf("%.2f%%", usedPercent),
}, nil
default:
return false, nil, errors.Join(ErrInvalidRule, ErrInvalidThresholdType)
return false, nil, errors.Join(ErrInvalidRule, fmt.Errorf("invalid threshold type for cpu: %s", t.ThresholdType.Name()))
}
}
func (r *Rule) shouldNotifyMemory(s *MemStatus, t *Threshold) (bool, *PushFormatArgs, error) {
@ -123,7 +122,7 @@ func (r *Rule) shouldNotifyMemory(s *MemStatus, t *Threshold) (bool, *PushFormat
case ThresholdTypeSize:
ok, err := t.True(size)
if err != nil {
return false, nil, errors.Join(ErrInvalidRule, err)
return false, nil, err
}
return ok, &PushFormatArgs{
Key: r.Matcher + "of Memory",
@ -132,14 +131,14 @@ func (r *Rule) shouldNotifyMemory(s *MemStatus, t *Threshold) (bool, *PushFormat
case ThresholdTypePercent:
ok, err := t.True(percent)
if err != nil {
return false, nil, errors.Join(ErrInvalidRule, err)
return false, nil, err
}
return ok, &PushFormatArgs{
Key: r.Matcher + "of Memory",
Value: fmt.Sprintf("%.2f%%", percent*100),
}, nil
default:
return false, nil, errors.Join(ErrInvalidRule, ErrInvalidThresholdType)
return false, nil, errors.Join(ErrInvalidRule, fmt.Errorf("invalid threshold type for memory: %s", t.ThresholdType.Name()))
}
}
func (r *Rule) shouldNotifySwap(s *SwapStatus, t *Threshold) (bool, *PushFormatArgs, error) {
@ -164,7 +163,7 @@ func (r *Rule) shouldNotifySwap(s *SwapStatus, t *Threshold) (bool, *PushFormatA
case ThresholdTypeSize:
ok, err := t.True(size)
if err != nil {
return false, nil, errors.Join(ErrInvalidRule, err)
return false, nil, err
}
return ok, &PushFormatArgs{
Key: r.Matcher + "of Swap",
@ -173,14 +172,14 @@ func (r *Rule) shouldNotifySwap(s *SwapStatus, t *Threshold) (bool, *PushFormatA
case ThresholdTypePercent:
ok, err := t.True(percent)
if err != nil {
return false, nil, errors.Join(ErrInvalidRule, err)
return false, nil, err
}
return ok, &PushFormatArgs{
Key: r.Matcher + "of Swap",
Value: fmt.Sprintf("%.2f%%", percent*100),
}, nil
default:
return false, nil, errors.Join(ErrInvalidRule, ErrInvalidThresholdType)
return false, nil, errors.Join(ErrInvalidRule, fmt.Errorf("invalid threshold type for swap: %s", t.ThresholdType.Name()))
}
}
func (r *Rule) shouldNotifyDisk(s []DiskStatus, t *Threshold) (bool, *PushFormatArgs, error) {
@ -205,7 +204,7 @@ func (r *Rule) shouldNotifyDisk(s []DiskStatus, t *Threshold) (bool, *PushFormat
case ThresholdTypeSize:
ok, err := t.True(disk.Used)
if err != nil {
return false, nil, errors.Join(ErrInvalidRule, err)
return false, nil, err
}
return ok, &PushFormatArgs{
Key: r.Matcher,
@ -214,14 +213,14 @@ func (r *Rule) shouldNotifyDisk(s []DiskStatus, t *Threshold) (bool, *PushFormat
case ThresholdTypePercent:
ok, err := t.True(disk.UsedPercent)
if err != nil {
return false, nil, errors.Join(ErrInvalidRule, err)
return false, nil, err
}
return ok, &PushFormatArgs{
Key: r.Matcher,
Value: fmt.Sprintf("%.2f%%", disk.UsedPercent),
}, nil
default:
return false, nil, errors.Join(ErrInvalidRule, ErrInvalidThresholdType)
return false, nil, errors.Join(ErrInvalidRule, fmt.Errorf("invalid threshold type for disk: %s", t.ThresholdType.Name()))
}
}
func (r *Rule) shouldNotifyNetwork(s []NetworkStatus, t *Threshold) (bool, *PushFormatArgs, error) {
@ -253,32 +252,48 @@ func (r *Rule) shouldNotifyNetwork(s []NetworkStatus, t *Threshold) (bool, *Push
}
switch t.ThresholdType {
case ThresholdTypeSize:
case ThresholdTypeSpeed:
speed := Size(0)
if in {
s, err := net.ReceiveSpeed()
if err != nil {
utils.Warn("[NETWORK] get receive speed failed: %s", err)
return false, nil, err
}
speed += s
}
if out {
s, err := net.TransmitSpeed()
if err != nil {
utils.Warn("[NETWORK] get transmit speed failed: %s", err)
return false, nil, err
}
speed += s
}
ok, err := t.True(speed)
if err != nil {
return false, nil, errors.Join(ErrInvalidRule, err)
return false, nil, err
}
return ok, &PushFormatArgs{
Key: r.Matcher,
Value: speed.String(),
}, nil
case ThresholdTypeSize:
size := Size(0)
if in {
size += net.TimeSequence.New.Receive
}
if out {
size += net.TimeSequence.New.Transmit
}
ok, err := t.True(size)
if err != nil {
return false, nil, err
}
return ok, &PushFormatArgs{
Key: r.Matcher,
Value: size.String(),
}, nil
default:
return false, nil, errors.Join(ErrInvalidRule, ErrInvalidThresholdType)
return false, nil, errors.Join(ErrInvalidRule, fmt.Errorf("invalid threshold type for network: %s", t.ThresholdType.Name()))
}
}
@ -305,14 +320,14 @@ func (r *Rule) shouldNotifyTemperature(s []TemperatureStatus, t *Threshold) (boo
case ThresholdTypeSize:
ok, err := t.True(temp.Value)
if err != nil {
return false, nil, errors.Join(ErrInvalidRule, err)
return false, nil, err
}
return ok, &PushFormatArgs{
Key: r.Matcher,
Value: fmt.Sprintf("%.2f°C", temp.Value),
}, nil
default:
return false, nil, errors.Join(ErrInvalidRule, ErrInvalidThresholdType)
return false, nil, errors.Join(ErrInvalidRule, fmt.Errorf("invalid threshold type for temperature: %s", t.ThresholdType.Name()))
}
}

View File

@ -3,6 +3,8 @@ package model
import (
"errors"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
@ -84,11 +86,11 @@ type SwapStatus struct {
}
type DiskStatus struct {
MountPath string
Filesystem string
Total Size
Used Size
Avail Size
MountPath string
Filesystem string
Total Size
Used Size
Avail Size
UsedPercent float64
}
@ -117,17 +119,19 @@ func (ns *NetworkStatus) ReceiveSpeed() (Size, error) {
}
func RefreshStatus() error {
stdout, stderr, err := utils.Execute("bash", res.ServerBoxShellPath)
output, _ := utils.Execute("bash", res.ServerBoxShellPath)
err := os.WriteFile(filepath.Join(res.ServerBoxDirPath, "shell_output.log"), []byte(output), 0644)
if err != nil {
utils.Warn("run shell failed: %s, %s", err, stderr)
utils.Warn("[STATUS] write shell output log failed: %s", err)
}
return ParseStatus(stdout)
return ParseStatus(output)
}
func ParseStatus(s string) error {
segments := strings.Split(s, "SrvBox")
for i, segment := range segments {
segments[i] = strings.TrimSpace(segment)
for i := range segments {
segments[i] = strings.TrimSpace(segments[i])
segments[i] = strings.Trim(segments[i], "\n")
}
if len(segments) != 10 {
return ErrInvalidShellOutput
@ -148,11 +152,11 @@ func ParseStatus(s string) error {
if err != nil {
utils.Warn("parse temperature status failed: %s", err)
}
err = parseDiskStatus(segments[5])
err = parseDiskStatus(segments[6])
if err != nil {
utils.Warn("parse disk status failed: %s", err)
}
return ErrRunShellFailed
return nil
}
func initMem() {
@ -170,54 +174,37 @@ func parseMemAndSwapStatus(s string) error {
lines := strings.Split(s, "\n")
for i := range lines {
line := strings.TrimSpace(lines[i])
value, err := strconv.ParseInt(strings.Fields(line)[1], 10, 64)
if err != nil {
return err
}
size := Size(value)
switch true {
case strings.HasPrefix(line, "MemTotal:"):
initMem()
size, err := ParseToSize(strings.Split(line, " ")[1])
if err != nil {
return err
}
status.Mem.Total = size
fallthrough
case strings.HasPrefix(line, "MemFree:"):
initMem()
size, err := ParseToSize(strings.Split(line, " ")[1])
if err != nil {
return err
}
status.Mem.Free = size
status.Mem.Used = status.Mem.Total - status.Mem.Free
fallthrough
case strings.HasPrefix(line, "MemAvailable:"):
initMem()
size, err := ParseToSize(strings.Split(line, " ")[1])
if err != nil {
return err
}
status.Mem.Avail = size
case strings.HasPrefix(line, "SwapTotal:"):
initSwap()
size, err := ParseToSize(strings.Split(line, " ")[1])
if err != nil {
return err
}
status.Swap.Total = size
fallthrough
case strings.HasPrefix(line, "SwapFree:"):
initSwap()
size, err := ParseToSize(strings.Split(line, " ")[1])
if err != nil {
return err
}
status.Swap.Free = size
status.Swap.Used = status.Swap.Total - status.Swap.Free
fallthrough
case strings.HasPrefix(line, "SwapCached:"):
initSwap()
size, err := ParseToSize(strings.Split(line, " ")[1])
if err != nil {
return err
}
status.Swap.Cached = size
}
}
@ -227,7 +214,7 @@ func parseMemAndSwapStatus(s string) error {
func parseCPUStatus(s string) error {
lines := strings.Split(strings.TrimSpace(s), "\n")
count := len(lines)
if status.CPU == nil || len(status.CPU) != count {
if len(status.CPU) != count {
status.CPU = make([]CPUStatus, count)
}
for i := range lines {
@ -263,18 +250,20 @@ func parseCPUStatus(s string) error {
}
func parseDiskStatus(s string) error {
liens := strings.Split(strings.TrimSpace(s), "\n")
count := len(liens)
if status.Disk == nil || len(status.Disk) != count {
lines := strings.Split(strings.TrimSpace(s), "\n")
lines = lines[1:]
count := len(lines)
if len(status.Disk) != count {
status.Disk = make([]DiskStatus, count)
}
for i := range liens {
line := strings.TrimSpace(liens[i])
for i := range lines {
line := strings.TrimSpace(lines[i])
fields := strings.Fields(line)
if len(fields) != 6 {
return errors.Join(ErrInvalidShellOutput, fmt.Errorf("invalid disk status: %s", line))
}
status.Disk[i].MountPath = fields[5]
status.Disk[i].Filesystem = fields[0]
total, err := ParseToSize(fields[1])
if err != nil {
return err
@ -305,7 +294,7 @@ func parseTemperatureStatus(s1, s2 string) error {
return errors.Join(ErrInvalidShellOutput, fmt.Errorf("invalid temperature status: %s, %s", s1, s2))
}
count := len(types)
if status.Temperature == nil || len(status.Temperature) != count {
if len(status.Temperature) != count {
status.Temperature = make([]TemperatureStatus, count)
}
for i := range types {
@ -322,8 +311,8 @@ func parseTemperatureStatus(s1, s2 string) error {
func parseNetworkStatus(s string) error {
lines := strings.Split(strings.TrimSpace(s), "\n")
count := len(lines)
if status.Network == nil || len(status.Network) != count - 2 {
status.Network = make([]NetworkStatus, count - 2)
if len(status.Network) != count-2 {
status.Network = make([]NetworkStatus, count-2)
}
for i := range lines {
if i < 2 {

View File

@ -25,20 +25,22 @@ func ParseToThreshold(s string) (*Threshold, error) {
var startIdx, endIdx int
// 判断阈值类型
if utils.Contains(runes, '%') {
if utils.Contains(runes, '%') { // 10%
thresholdType = ThresholdTypePercent
endIdx = runesLen - 1
} else if utils.Contains(sizeSuffix, string(runes[len(runes)-1])) {
if strings.HasSuffix(s, "/s") {
thresholdType = ThresholdTypeSpeed
// 10m/s -> m/s -> 3
endIdx = runesLen - 3
} else {
// 10m -> m -> 1
thresholdType = ThresholdTypeSize
endIdx = runesLen - 1
}
} else if strings.HasSuffix(s, "c") {
} else if strings.HasSuffix(s, "/s") { // 10m/s
thresholdType = ThresholdTypeSpeed
// 10m/s -> m/s -> 3
endIdx = runesLen - 3
} else if utils.Contains(sizeSuffix, string(runes[runesLen-1])) { // 10m
// 10m -> m -> 1
thresholdType = ThresholdTypeSize
endIdx = runesLen - 1
} else if strings.HasSuffix(s, "c") { // 32c
thresholdType = ThresholdTypeTemperature
// 32c -> c -> 1
endIdx = runesLen - 1
@ -79,24 +81,31 @@ func ParseToThreshold(s string) (*Threshold, error) {
func (t *Threshold) True(now any) (bool, error) {
switch t.ThresholdType {
case ThresholdTypePercent:
now, ok := now.(float64)
if !ok {
return false, errors.Join(ErrInvalidRule, fmt.Errorf("%v is not float64", now))
case ThresholdTypePercent, ThresholdTypeTemperature:
var nowValue float64
switch now.(type) {
case float64:
nowValue = now.(float64)
case int:
nowValue = float64(now.(int))
case int64:
nowValue = float64(now.(int64))
default:
return false, errors.Join(ErrInvalidRule, fmt.Errorf("%v is %T", now, now))
}
switch t.CompareType {
case CompareTypeLess:
return now < t.Value, nil
return nowValue < t.Value, nil
case CompareTypeLessOrEqual:
return now <= t.Value, nil
return nowValue <= t.Value, nil
case CompareTypeEqual:
return now == t.Value, nil
return nowValue == t.Value, nil
case CompareTypeGreaterOrEqual:
return now >= t.Value, nil
return nowValue >= t.Value, nil
case CompareTypeGreater:
return now > t.Value, nil
return nowValue > t.Value, nil
}
case ThresholdTypeSize:
case ThresholdTypeSize, ThresholdTypeSpeed:
now, ok := now.(Size)
if !ok {
return false, errors.Join(ErrInvalidRule, fmt.Errorf("%v is not Size", now))
@ -115,7 +124,7 @@ func (t *Threshold) True(now any) (bool, error) {
return nowFloat64 > t.Value, nil
}
}
return false, ErrCompareFailed
return false, fmt.Errorf("not support %#v", t)
}
type CompareType uint8
@ -129,7 +138,6 @@ const (
)
type ThresholdType uint8
const (
ThresholdTypeUnknown ThresholdType = iota
// eg: 80% 80.001%
@ -142,3 +150,16 @@ const (
// eg: 32c
ThresholdTypeTemperature
)
func (tt *ThresholdType) Name() string {
switch *tt {
case ThresholdTypePercent:
return "percent"
case ThresholdTypeSize:
return "size"
case ThresholdTypeSpeed:
return "speed"
case ThresholdTypeTemperature:
return "temperature"
}
return "unknown"
}

View File

@ -3,7 +3,7 @@
export LANG=en_US.utf-8
echo SrvBox
cat /proc/net/dev && date +%s
cat /proc/net/dev
echo SrvBox
cat /etc/os-release | grep PRETTY_NAME
echo SrvBox

View File

@ -2,6 +2,7 @@ package runner
import (
"os"
"strings"
"sync"
"time"
@ -11,15 +12,11 @@ import (
)
var (
pushArgs = []*model.PushFormatArgs{}
pushArgs = []*model.PushFormatArgs{}
pushArgsLock = new(sync.RWMutex)
)
func init() {
if utils.Exist(res.ServerBoxShellPath) {
return
}
scriptBytes, err := res.Files.ReadFile(res.ServerBoxShellFileName)
if err != nil {
utils.Error("[INIT] Read embed file error: %v", err)
@ -57,7 +54,9 @@ func Run() {
for _, rule := range model.Config.Rules {
notify, arg, err := rule.ShouldNotify(status)
if err != nil {
utils.Warn("[RULE] %s error: %v", rule.Id(), err)
if !strings.Contains(err.Error(), "not ready") {
utils.Warn("[RULE] %s error: %v", rule.Id(), err)
}
}
if notify && arg != nil {
@ -84,7 +83,7 @@ func Push() {
time.Sleep(model.DefaultappConfig.GetRunInterval())
continue
}
for _, push := range model.Config.Pushes {
pushArgsLock.RLock()
err := push.Push(pushArgs)

View File

@ -1,7 +1,6 @@
package utils
import (
"bytes"
"os/exec"
)
@ -14,11 +13,8 @@ func Contains[T string | int | float64 | rune](slice []T, item T) bool {
return false
}
func Execute(bin string, args ...string) (string, string, error) {
func Execute(bin string, args ...string) (string, error) {
cmd := exec.Command(bin, args...)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
return string(stdout.Bytes()), string(stderr.Bytes()), err
output, err := cmd.CombinedOutput()
return string(output), err
}