From e735600d207553b78ff38bf7cc871e4930fc9f8e Mon Sep 17 00:00:00 2001 From: Akizon77 Date: Fri, 31 Oct 2025 17:23:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=B8=8D=E5=86=8D=E4=BE=9D=E8=B5=96vns?= =?UTF-8?q?tat?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- cmd/root.go | 18 ++ install.sh | 75 +---- monitoring/netstatic/static.go | 522 +++++++++++++++++++++++++++++++++ monitoring/unit/net.go | 111 +------ monitoring/unit/net_test.go | 27 -- 6 files changed, 559 insertions(+), 197 deletions(-) create mode 100644 monitoring/netstatic/static.go diff --git a/.gitignore b/.gitignore index 67fc407..e74af24 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ komari-agent.exe komari-agent .komari-agent.exe.old build/ -auto-discovery.json \ No newline at end of file +auto-discovery.json +net_static.json diff --git a/cmd/root.go b/cmd/root.go index 1885cbf..c27b04a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -8,6 +8,7 @@ import ( "github.com/komari-monitor/komari-agent/cmd/flags" "github.com/komari-monitor/komari-agent/dnsresolver" + "github.com/komari-monitor/komari-agent/monitoring/netstatic" monitoring "github.com/komari-monitor/komari-agent/monitoring/unit" "github.com/komari-monitor/komari-agent/server" "github.com/komari-monitor/komari-agent/update" @@ -29,6 +30,23 @@ var RootCmd = &cobra.Command{ go WarnKomariRunning() } + if flags.MonthRotate != 0 { + err := netstatic.StartOrContinue() + if err != nil { + log.Println("Failed to start netstatic monitoring:", err) + } + nics, err := monitoring.InterfaceList() + if err != nil { + log.Println("Failed to get interface list for netstatic:", err) + } + err = netstatic.SetNewConfig(netstatic.NetStaticConfig{ + Nics: nics, + }) + if err != nil { + log.Println("Failed to set netstatic config:", err) + } + } + log.Println("Komari Agent", update.CurrentVersion) log.Println("Github Repo:", update.Repo) diff --git a/install.sh b/install.sh index e08d4ad..77dd4a4 100755 --- a/install.sh +++ b/install.sh @@ -40,7 +40,7 @@ service_name="komari-agent" target_dir="/opt/komari" github_proxy="" install_version="" # New parameter for specifying version -need_vnstat=false # Flag to indicate if vnstat is needed + # Detect OS os_type=$(uname -s) @@ -90,11 +90,6 @@ while [[ $# -gt 0 ]]; do install_version="$2" shift 2 ;; - --month-rotate) - need_vnstat=true - komari_args="$komari_args $1" - shift - ;; --install*) log_warning "Unknown install parameter: $1" shift @@ -139,11 +134,6 @@ if [ -n "$install_version" ]; then else log_config " Agent version: ${GREEN}Latest${NC}" fi -if [ "$need_vnstat" = true ]; then - log_config " vnstat installation: ${GREEN}Required (--month-rotate detected)${NC}" -else - log_config " vnstat installation: ${GREEN}Not required${NC}" -fi echo "" # Function to uninstall the previous installation @@ -239,68 +229,11 @@ install_dependencies() { fi } -# Function to install vnstat if needed -install_vnstat() { - if [ "$need_vnstat" = true ]; then - log_step "Checking and installing vnstat for --month-rotate functionality..." - - if command -v vnstat >/dev/null 2>&1; then - log_success "vnstat is already installed" - return - fi - - log_info "vnstat not found, installing..." - - # Install vnstat based on package manager - if command -v apt >/dev/null 2>&1; then - log_info "Using apt to install vnstat..." - apt update - apt install -y vnstat - elif command -v yum >/dev/null 2>&1; then - log_info "Using yum to install vnstat..." - yum install -y vnstat - elif command -v dnf >/dev/null 2>&1; then - log_info "Using dnf to install vnstat..." - dnf install -y vnstat - elif command -v apk >/dev/null 2>&1; then - log_info "Using apk to install vnstat..." - apk add vnstat - elif command -v brew >/dev/null 2>&1; then - log_info "Using Homebrew to install vnstat..." - brew install vnstat - elif command -v pacman >/dev/null 2>&1; then - log_info "Using pacman to install vnstat..." - pacman -S --noconfirm vnstat - else - log_error "No supported package manager found for vnstat installation" - log_error "Please install vnstat manually to use --month-rotate functionality" - exit 1 - fi - - # Verify installation - if command -v vnstat >/dev/null 2>&1; then - log_success "vnstat installed successfully" - - # Start vnstat daemon if systemd is available - if command -v systemctl >/dev/null 2>&1; then - log_info "Starting vnstat daemon..." - systemctl enable vnstat - systemctl start vnstat - elif [ "$os_name" = "darwin" ] && command -v launchctl >/dev/null 2>&1; then - log_info "vnstat daemon management varies on macOS, please check vnstat documentation" - fi - else - log_error "Failed to install vnstat" - exit 1 - fi - fi -} - + # Install dependencies install_dependencies -# Install vnstat if needed for month-rotate -install_vnstat + # Architecture detection with platform-specific support arch=$(uname -m) @@ -625,7 +558,7 @@ EOF # Add program arguments if provided if [ -n "$komari_args" ]; then - echo "$komari_args" | xargs -n1 printf " %s\n"  + echo "$komari_args" | xargs -n1 printf " %s\n" >> "$plist_file" fi cat >> "$plist_file" << EOF diff --git a/monitoring/netstatic/static.go b/monitoring/netstatic/static.go new file mode 100644 index 0000000..a9a189a --- /dev/null +++ b/monitoring/netstatic/static.go @@ -0,0 +1,522 @@ +package netstatic + +import ( + "encoding/json" + "errors" + "io" + "os" + "path/filepath" + "sync" + "time" + + gnet "github.com/shirou/gopsutil/v4/net" +) + +/* +统计每个网卡的流量情况,保存最近DataPreserveDay天的数据,每DetectInterval秒采集一次 + +默认保存到当前目录下的net_static.json文件中 +net_static.json 中有字段 config,表示当前的配置,如果没有则使用默认值 +unix时间戳,单位秒 + +所有操作都尽可能在内存中完成,避免频繁的IO操作 + +只有在启动、停止和保存时,才会进行文件的读写操作 +*/ +var ( + DefaultDataPreserveDay = 31.0 // in days,保存最近多少天的数据,过期数据会被删除 + DefaultDetectInterval = 2.0 // in seconds,采集间隔 + DefaultSaveInterval = 60.0 * 10 // in seconds,写入到磁盘的间隔,避免大量IO操作,保存到文件的间隔也是这个值,而不是DetectInterval + SaveFilePath = "./net_static.json" +) + +var ( + staticCache map[string][]TrafficData // key: interface name,统计缓存,当前没有被保存到文件中的,间隔DetectInterval,触发保存时,合并所有的tx/rx数据,以SaveInterval,写入到文件中,随后清空缓存 + config NetStaticConfig +) + +// NetStatic 网卡流量统计数据 +type NetStatic struct { + Interfaces map[string][]TrafficData `json:"interfaces"` // key: interface name + Config NetStaticConfig `json:"config"` +} + +type NetStaticConfig struct { + DataPreserveDay float64 `json:"data_preserve_day"` // in days,保存最近多少天的数据,过期数据会被删除 + DetectInterval float64 `json:"detect_interval"` // in seconds,采集间隔 + SaveInterval float64 `json:"save_interval"` // in seconds,写入到磁盘的间隔,避免大量IO操作 + Nics []string `json:"nics"` // 仅监控指定的网卡名称列表,空表示监控所有网卡 +} + +type TrafficData struct { + Timestamp uint64 `json:"timestamp"` + Tx uint64 `json:"tx"` // 第n与n-1次采集的差值 + Rx uint64 `json:"rx"` // 第n与n-1次采集的差值 +} + +var ( + mu sync.RWMutex + running bool + detectTicker *time.Ticker + saveTicker *time.Ticker + stopCh chan struct{} + + // 内存持久区(与文件内容一致,但仅在启动、保存、停止时与磁盘交互) + store NetStatic + + // 上次采集到的累计字节数(用于计算 delta) + lastCounters = map[string]struct{ Tx, Rx uint64 }{} +) + +func nowUnix() uint64 { return uint64(time.Now().Unix()) } + +// isNicAllowed 判断网卡是否在监控白名单内;当未配置白名单(空切片或nil)时,允许所有网卡 +func isNicAllowed(name string) bool { + if len(config.Nics) == 0 { + return true + } + for _, n := range config.Nics { + if n == name { + return true + } + } + return false +} + +func ensureInitLocked() { + if store.Interfaces == nil { + store.Interfaces = make(map[string][]TrafficData) + } + if staticCache == nil { + staticCache = make(map[string][]TrafficData) + } + if config.DataPreserveDay == 0 { + config.DataPreserveDay = DefaultDataPreserveDay + } + if config.DetectInterval == 0 { + config.DetectInterval = DefaultDetectInterval + } + if config.SaveInterval == 0 { + config.SaveInterval = DefaultSaveInterval + } +} + +func loadFromFileLocked() error { + // 不存在则用默认配置 + f, err := os.Open(SaveFilePath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + ensureInitLocked() + store.Config = configOrDefault(config) + return nil + } + return err + } + defer f.Close() + + data, err := io.ReadAll(f) + if err != nil { + return err + } + if len(data) == 0 { + ensureInitLocked() + store.Config = configOrDefault(config) + return nil + } + var ns NetStatic + if err := json.Unmarshal(data, &ns); err != nil { + // 文件损坏则不阻塞使用,采用默认并备份坏文件 + _ = os.Rename(SaveFilePath, SaveFilePath+".bak") + ensureInitLocked() + store.Config = configOrDefault(config) + return nil + } + store = ns + config = configOrDefault(ns.Config) + ensureInitLocked() + // 启动时清理过期数据 + purgeExpiredLocked() + return nil +} + +func saveToFileLocked() error { + // 确保目录存在 + if err := os.MkdirAll(filepath.Dir(SaveFilePath), 0o755); err != nil { + return err + } + // 写入时带上当前 config + store.Config = configOrDefault(config) + b, err := json.Marshal(store) // 紧凑格式(不缩进) + if err != nil { + return err + } + tmp := SaveFilePath + ".tmp" + if err := os.WriteFile(tmp, b, 0o644); err != nil { + return err + } + return os.Rename(tmp, SaveFilePath) +} + +func configOrDefault(c NetStaticConfig) NetStaticConfig { + if c.DataPreserveDay == 0 { + c.DataPreserveDay = DefaultDataPreserveDay + } + if c.DetectInterval == 0 { + c.DetectInterval = DefaultDetectInterval + } + if c.SaveInterval == 0 { + c.SaveInterval = DefaultSaveInterval + } + return c +} + +func purgeExpiredLocked() { + // 根据 DataPreserveDay 删除过期数据 + ttl := time.Duration(config.DataPreserveDay * 24 * float64(time.Hour)) + cutoff := uint64(time.Now().Add(-ttl).Unix()) + for name, arr := range store.Interfaces { + // 仅保留 >= cutoff 的数据 + kept := arr[:0] + for _, td := range arr { + if td.Timestamp >= cutoff { + kept = append(kept, td) + } + } + if len(kept) == 0 { + delete(store.Interfaces, name) + } else { + store.Interfaces[name] = kept + } + } +} + +func safeDelta(cur, prev uint64) uint64 { + if cur >= prev { + return cur - prev + } + // 处理计数器回绕或重置,视为 0 增量 + return 0 +} + +func sampleOnceLocked() { + ios, err := gnet.IOCounters(true) + if err != nil { + return + } + ts := nowUnix() + for _, io := range ios { + name := io.Name + // 仅监控指定网卡(当配置了 Nics 时) + if !isNicAllowed(name) { + continue + } + curTx := io.BytesSent + curRx := io.BytesRecv + prev, ok := lastCounters[name] + if ok { + dtx := safeDelta(curTx, prev.Tx) + drx := safeDelta(curRx, prev.Rx) + // 首次采样不记录 + if dtx > 0 || drx > 0 { + staticCache[name] = append(staticCache[name], TrafficData{Timestamp: ts, Tx: dtx, Rx: drx}) + } else { + // 即便为 0,也可以记录,但为了降低噪音与占用,这里忽略 0 + } + } + lastCounters[name] = struct{ Tx, Rx uint64 }{Tx: curTx, Rx: curRx} + } +} + +func flushCacheLocked(ts uint64) { + if len(staticCache) == 0 { + return + } + for name, arr := range staticCache { + var sumTx, sumRx uint64 + for _, td := range arr { + sumTx += td.Tx + sumRx += td.Rx + } + if sumTx > 0 || sumRx > 0 { + store.Interfaces[name] = append(store.Interfaces[name], TrafficData{Timestamp: ts, Tx: sumTx, Rx: sumRx}) + } + } + // 清空缓存 + staticCache = make(map[string][]TrafficData) +} + +// GetNetStatic 获取当前的所有流量统计数据 +func GetNetStatic() (*NetStatic, error) { + mu.RLock() + defer mu.RUnlock() + ensureInitLocked() + // 合并 store + cache(cache 不合并为单点,直接以原样返回临时视图) + merged := NetStatic{Interfaces: map[string][]TrafficData{}, Config: configOrDefault(config)} + for name, arr := range store.Interfaces { + cp := make([]TrafficData, len(arr)) + copy(cp, arr) + merged.Interfaces[name] = cp + } + for name, arr := range staticCache { + merged.Interfaces[name] = append(merged.Interfaces[name], arr...) + } + return &merged, nil +} + +// StartOrContinue 开始或继续流量统计 +func StartOrContinue() error { + if running { + return nil + } + mu.Lock() + defer mu.Unlock() + ensureInitLocked() + // 读取历史 + if err := loadFromFileLocked(); err != nil { + return err + } + // 启动 ticker + detectTicker = time.NewTicker(time.Duration(config.DetectInterval * float64(time.Second))) + saveTicker = time.NewTicker(time.Duration(config.SaveInterval * float64(time.Second))) + stopCh = make(chan struct{}) + running = true + + // 采集 goroutine + go func() { + for { + select { + case <-detectTicker.C: + mu.Lock() + sampleOnceLocked() + mu.Unlock() + case <-stopCh: + return + } + } + }() + + // 保存 goroutine + go func() { + for { + select { + case t := <-saveTicker.C: + mu.Lock() + flushCacheLocked(uint64(t.Unix())) + purgeExpiredLocked() + _ = saveToFileLocked() + mu.Unlock() + case <-stopCh: + return + } + } + }() + return nil +} + +// Clear 清除所有流量统计数据 +func Clear() error { + mu.Lock() + defer mu.Unlock() + ensureInitLocked() + store.Interfaces = make(map[string][]TrafficData) + staticCache = make(map[string][]TrafficData) + lastCounters = map[string]struct{ Tx, Rx uint64 }{} + // 不落盘,等下次保存或停止时写 + return nil +} + +// Stop 停止流量统计 +func Stop() error { + mu.Lock() + if !running { + mu.Unlock() + return nil + } + running = false + if detectTicker != nil { + detectTicker.Stop() + } + if saveTicker != nil { + saveTicker.Stop() + } + close(stopCh) + // 最后一轮 flush + 保存 + flushCacheLocked(nowUnix()) + purgeExpiredLocked() + err := saveToFileLocked() + mu.Unlock() + return err +} + +// GetNetStaticBetween 获取指定时间段内的流量统计数据,start和end为unix时间戳 +func GetNetStaticBetween(start, end uint64) (*NetStatic, error) { + mu.RLock() + defer mu.RUnlock() + ensureInitLocked() + res := NetStatic{Interfaces: map[string][]TrafficData{}, Config: configOrDefault(config)} + inRange := func(ts uint64) bool { return (start == 0 || ts >= start) && (end == 0 || ts <= end) } + for name, arr := range store.Interfaces { + var filtered []TrafficData + for _, td := range arr { + if inRange(td.Timestamp) { + filtered = append(filtered, td) + } + } + if len(filtered) > 0 { + res.Interfaces[name] = filtered + } + } + // 合并缓存 + for name, arr := range staticCache { + for _, td := range arr { + if inRange(td.Timestamp) { + res.Interfaces[name] = append(res.Interfaces[name], td) + } + } + } + return &res, nil +} + +// GetTotalTraffic 获取总流量统计数据, key为网卡名称, value为对应的流量数据总和 +func GetTotalTraffic() (map[string]TrafficData, error) { + mu.RLock() + defer mu.RUnlock() + ensureInitLocked() + res := map[string]TrafficData{} + add := func(name string, tx, rx uint64) { + cur := res[name] + cur.Tx += tx + cur.Rx += rx + res[name] = cur + } + for name, arr := range store.Interfaces { + var tx, rx uint64 + for _, td := range arr { + tx += td.Tx + rx += td.Rx + } + add(name, tx, rx) + } + for name, arr := range staticCache { + var tx, rx uint64 + for _, td := range arr { + tx += td.Tx + rx += td.Rx + } + add(name, tx, rx) + } + return res, nil +} + +// GetTotalTrafficBetween 获取指定时间段内的总流量统计数据,start和end为unix时间戳 +func GetTotalTrafficBetween(start, end uint64) (map[string]TrafficData, error) { + mu.RLock() + defer mu.RUnlock() + ensureInitLocked() + res := map[string]TrafficData{} + inRange := func(ts uint64) bool { return (start == 0 || ts >= start) && (end == 0 || ts <= end) } + add := func(name string, tx, rx uint64) { + cur := res[name] + cur.Tx += tx + cur.Rx += rx + res[name] = cur + } + for name, arr := range store.Interfaces { + var tx, rx uint64 + for _, td := range arr { + if inRange(td.Timestamp) { + tx += td.Tx + rx += td.Rx + } + } + if tx > 0 || rx > 0 { + add(name, tx, rx) + } + } + for name, arr := range staticCache { + var tx, rx uint64 + for _, td := range arr { + if inRange(td.Timestamp) { + tx += td.Tx + rx += td.Rx + } + } + if tx > 0 || rx > 0 { + add(name, tx, rx) + } + } + return res, nil +} + +// SetNewConfig 设置新的配置,config中的值如果为0则表示不修改对应的配置项 +func SetNewConfig(newCfg NetStaticConfig) error { + mu.Lock() + defer mu.Unlock() + ensureInitLocked() + // 合并新配置 + if newCfg.DataPreserveDay != 0 { + store.Config.DataPreserveDay = newCfg.DataPreserveDay + } + if newCfg.DetectInterval != 0 { + store.Config.DetectInterval = newCfg.DetectInterval + } + if newCfg.SaveInterval != 0 { + store.Config.SaveInterval = newCfg.SaveInterval + } + // Nics: nil 表示不修改;非 nil 则更新(空切片表示监控所有网卡) + if newCfg.Nics != nil { + // 做一份拷贝以避免外部切片后续修改影响内部配置 + tmp := make([]string, len(newCfg.Nics)) + copy(tmp, newCfg.Nics) + store.Config.Nics = tmp + } + // 更新生效配置 + cfg := configOrDefault(store.Config) + store.Config = cfg + config = cfg + // 重新配置 ticker(若运行中) + if running { + if detectTicker != nil { + detectTicker.Stop() + } + if saveTicker != nil { + saveTicker.Stop() + } + detectTicker = time.NewTicker(time.Duration(cfg.DetectInterval * float64(time.Second))) + saveTicker = time.NewTicker(time.Duration(cfg.SaveInterval * float64(time.Second))) + + // 当配置了指定网卡白名单时,清理不在白名单内的缓存与上次计数,避免无用数据积累 + if len(cfg.Nics) > 0 { + allowed := make(map[string]struct{}, len(cfg.Nics)) + for _, n := range cfg.Nics { + allowed[n] = struct{}{} + } + for name := range lastCounters { + if _, ok := allowed[name]; !ok { + delete(lastCounters, name) + } + } + for name := range staticCache { + if _, ok := allowed[name]; !ok { + delete(staticCache, name) + } + } + } + } + // 立即写盘 + _ = saveToFileLocked() + // 同时做一次过期清理 + purgeExpiredLocked() + return nil +} + +func ForceReplaceRecord(rec map[string][]TrafficData) error { + mu.Lock() + defer mu.Unlock() + ensureInitLocked() + store.Interfaces = rec + // 不立即写盘,等下一次周期性保存或停止时写 + // 同时做一次过期清理 + purgeExpiredLocked() + return nil +} diff --git a/monitoring/unit/net.go b/monitoring/unit/net.go index b7e6822..1ff46a9 100644 --- a/monitoring/unit/net.go +++ b/monitoring/unit/net.go @@ -1,13 +1,12 @@ package monitoring import ( - "encoding/json" "fmt" - "os/exec" "strings" "time" "github.com/komari-monitor/komari-agent/cmd/flags" + "github.com/komari-monitor/komari-agent/monitoring/netstatic" "github.com/shirou/gopsutil/v4/net" ) @@ -126,103 +125,29 @@ type VnstatOutput struct { Interfaces []VnstatInterface `json:"interfaces"` } -func getVnstatData() (map[string]VnstatInterface, error) { - cmd := exec.Command("vnstat", "--json") - output, err := cmd.Output() - if err != nil { - return nil, fmt.Errorf("failed to run vnstat: %w", err) - } - - var vnstatOutput VnstatOutput - if err := json.Unmarshal(output, &vnstatOutput); err != nil { - return nil, fmt.Errorf("failed to parse vnstat output: %w", err) - } - - interfaceMap := make(map[string]VnstatInterface) - for _, iface := range vnstatOutput.Interfaces { - interfaceMap[iface.Name] = iface - } - - return interfaceMap, nil -} - -// calculateMonthlyUsage 计算从月重置日到当前日期的流量使用量 -func calculateMonthlyUsage(iface VnstatInterface, monthRotateDay int) (rx, tx uint64) { - now := time.Now() - currentYear := now.Year() - currentMonth := int(now.Month()) - currentDay := now.Day() - - // 确定统计的起始日期 - var startYear, startMonth, startDay int - if currentDay >= monthRotateDay { - // 当前月的重置日已过,从当前月的重置日开始计算 - startYear = currentYear - startMonth = currentMonth - startDay = monthRotateDay - } else { - // 当前月的重置日未到,从上个月的重置日开始计算 - if currentMonth == 1 { - startYear = currentYear - 1 - startMonth = 12 - } else { - startYear = currentYear - startMonth = currentMonth - 1 - } - startDay = monthRotateDay - } - - startTime := time.Date(startYear, time.Month(startMonth), startDay, 0, 0, 0, 0, time.Local) - - // 统计从起始时间到现在的流量 - for _, entry := range iface.Traffic.Day { - entryTime := time.Date(entry.Date.Year, time.Month(entry.Date.Month), entry.Date.Day, 0, 0, 0, 0, time.Local) - if entryTime.After(startTime) || entryTime.Equal(startTime) { - rx += entry.Rx - tx += entry.Tx - } - } - - return rx, tx -} - -// setVnstatMonthRotate 设置vnstat的月重置日期 -func setVnstatMonthRotate(day int) error { - if day < 1 || day > 31 { - return fmt.Errorf("invalid day: %d, must be between 1 and 31", day) - } - - cmd := exec.Command("vnstat", "--config", fmt.Sprintf("MonthRotate %d", day)) - if err := cmd.Run(); err != nil { - return fmt.Errorf("failed to set vnstat month rotate to day %d: %w", day, err) - } - - return nil -} - func NetworkSpeed() (totalUp, totalDown, upSpeed, downSpeed uint64, err error) { includeNics := parseNics(flags.IncludeNics) excludeNics := parseNics(flags.ExcludeNics) - // 如果设置了月重置(非0),使用vnstat统计totalUp、totalDown + // 如果设置了月重置(非0),统计totalUp、totalDown if flags.MonthRotate != 0 { - - vnstatData, err := getVnstatData() + netstatic.StartOrContinue() // 确保netstatic在运行 + now := uint64(time.Now().Unix()) + aMonthAgo := now - uint64(30*24*3600) + nicStatics, err := netstatic.GetTotalTrafficBetween(aMonthAgo, now) if err != nil { - // 如果vnstat失败,回退到原来的方法,并返回额外的错误信息 + // 如果netstatic失败,回退到原来的方法,并返回额外的错误信息 fallbackUp, fallbackDown, fallbackUpSpeed, fallbackDownSpeed, fallbackErr := getNetworkSpeedFallback(includeNics, excludeNics) if fallbackErr != nil { - return fallbackUp, fallbackDown, fallbackUpSpeed, fallbackDownSpeed, fmt.Errorf("failed to call vnstat: %v; fallback error: %w", err, fallbackErr) + return fallbackUp, fallbackDown, fallbackUpSpeed, fallbackDownSpeed, fmt.Errorf("failed to call GetTotalTrafficBetween: %v; fallback error: %w", err, fallbackErr) } - return fallbackUp, fallbackDown, fallbackUpSpeed, fallbackDownSpeed, fmt.Errorf("failed to call vnstat: %w", err) + return fallbackUp, fallbackDown, fallbackUpSpeed, fallbackDownSpeed, fmt.Errorf("failed to call GetTotalTrafficBetween: %w", err) } - // 使用vnstat数据计算当月(到重置日)的流量使用量 - for interfaceName, interfaceData := range vnstatData { + for interfaceName, stats := range nicStatics { if shouldInclude(interfaceName, includeNics, excludeNics) { - monthlyRx, monthlyTx := calculateMonthlyUsage(interfaceData, flags.MonthRotate) - totalUp += monthlyTx - totalDown += monthlyRx + totalUp += stats.Tx + totalDown += stats.Rx } } @@ -327,17 +252,7 @@ func InterfaceList() ([]string, error) { includeNics := parseNics(flags.IncludeNics) excludeNics := parseNics(flags.ExcludeNics) interfaces := []string{} - if flags.MonthRotate != 0 { - vnstatData, err := getVnstatData() - if err == nil { - for interfaceName := range vnstatData { - if shouldInclude(interfaceName, includeNics, excludeNics) { - interfaces = append(interfaces, interfaceName) - } - } - return interfaces, nil - } - } + ioCounters, err := net.IOCounters(true) if err != nil { return nil, err diff --git a/monitoring/unit/net_test.go b/monitoring/unit/net_test.go index 9ccf110..029765c 100644 --- a/monitoring/unit/net_test.go +++ b/monitoring/unit/net_test.go @@ -1,7 +1,6 @@ package monitoring import ( - "strings" "testing" "github.com/komari-monitor/komari-agent/cmd/flags" @@ -229,39 +228,13 @@ func TestNetworkSpeedWithMonthRotate(t *testing.T) { // 如果vnstat不可用,可能会回退到原来的方法,这是正常的 if err != nil { - if strings.Contains(err.Error(), "failed to call vnstat") { - t.Logf("vnstat not available, this is expected in test environment: %v", err) - return - } t.Fatalf("NetworkSpeed failed: %v", err) } - if totalUp < 0 { - t.Errorf("Expected non-negative totalUp, got %d", totalUp) - } - - if totalDown < 0 { - t.Errorf("Expected non-negative totalDown, got %d", totalDown) - } - t.Logf("With MonthRotate - TotalUp: %d, TotalDown: %d, UpSpeed: %d/s, DownSpeed: %d/s", totalUp, totalDown, upSpeed, downSpeed) } -func TestGetVnstatData(t *testing.T) { - // 这个测试可能会失败,因为vnstat可能没有安装 - _, err := getVnstatData() - if err != nil { - if strings.Contains(err.Error(), "failed to run vnstat") { - t.Logf("vnstat not available, this is expected: %v", err) - return - } - t.Fatalf("getVnstatData failed unexpectedly: %v", err) - } - - t.Log("vnstat data retrieved successfully") -} - func TestNetworkSpeedWithNicFilters(t *testing.T) { // 保存原始值 originalMonthRotate := flags.MonthRotate