项目初始化

This commit is contained in:
lirui
2026-01-04 21:51:40 +08:00
commit 1ccff88c56
6 changed files with 399 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
{
"permissions": {
"allow": [
"Bash(mkdir:*)"
]
}
}

163
Makefile Normal file
View File

@@ -0,0 +1,163 @@
# 项目信息
BINARY_NAME=nodeprobe
MODULE_NAME=github.com/meowrain/nodeprobe
MAIN_PATH=./cmd/nodeprobe
# Go 相关命令
GO=go
GOFLAGS=-v
GOBUILD=$(GO) build $(GOFLAGS)
GOCLEAN=$(GO) clean
GOTEST=$(GO) test
GOGET=$(GO) get
GOMOD=$(GO) mod
# 输出目录
BUILD_DIR=build
DIST_DIR=dist
# 版本信息
VERSION?=dev
BUILD_TIME=$(shell date -u '+%Y-%m-%d_%H:%M:%S')
GIT_COMMIT=$(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
LDFLAGS=-ldflags "-X main.Version=$(VERSION) -X main.BuildTime=$(BUILD_TIME) -X main.GitCommit=$(GIT_COMMIT)"
# 默认目标
.PHONY: all
all: clean build
# 构建
.PHONY: build
build:
@echo "Building $(BINARY_NAME)..."
@mkdir -p $(BUILD_DIR)
$(GOBUILD) $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME)$(shell go env GOEXE) $(MAIN_PATH)
@echo "Build complete: $(BUILD_DIR)/$(BINARY_NAME)"
# 快速构建(不清理)
.PHONY: quick
quick:
@echo "Quick building $(BINARY_NAME)..."
@mkdir -p $(BUILD_DIR)
$(GOBUILD) -o $(BUILD_DIR)/$(BINARY_NAME)$(shell go env GOEXE) $(MAIN_PATH)
# 运行
.PHONY: run
run: build
@echo "Running $(BINARY_NAME)..."
@$(BUILD_DIR)/$(BINARY_NAME)$(shell go env GOEXE)
# 直接运行(不构建)
.PHONY: dev
dev:
@echo "Running $(BINARY_NAME) in development mode..."
$(GO) run $(MAIN_PATH)
# 测试
.PHONY: test
test:
@echo "Running tests..."
$(GOTEST) -v -cover ./...
# 测试覆盖率
.PHONY: coverage
coverage:
@echo "Running tests with coverage..."
$(GOTEST) -v -coverprofile=coverage.out ./...
$(GO) tool cover -html=coverage.out -o coverage.html
@echo "Coverage report generated: coverage.html"
# 清理
.PHONY: clean
clean:
@echo "Cleaning..."
$(GOCLEAN)
@rm -rf $(BUILD_DIR)
@rm -rf $(DIST_DIR)
@rm -f coverage.out coverage.html
@echo "Clean complete"
# 依赖管理
.PHONY: deps
deps:
@echo "Downloading dependencies..."
$(GOMOD) download
$(GOMOD) tidy
# 更新依赖
.PHONY: deps-update
deps-update:
@echo "Updating dependencies..."
$(GOGET) -u ./...
$(GOMOD) tidy
# 代码检查
.PHONY: lint
lint:
@echo "Running linter..."
@which golangci-lint > /dev/null || (echo "golangci-lint not found. Install it from https://golangci-lint.run/usage/install/" && exit 1)
golangci-lint run ./...
# 代码格式化
.PHONY: fmt
fmt:
@echo "Formatting code..."
$(GO) fmt ./...
# 代码审查
.PHONY: vet
vet:
@echo "Running go vet..."
$(GO) vet ./...
# 交叉编译(多平台)
.PHONY: build-all
build-all: clean
@echo "Building for multiple platforms..."
@mkdir -p $(DIST_DIR)
@echo "Building for Windows amd64..."
@GOOS=windows GOARCH=amd64 $(GOBUILD) $(LDFLAGS) -o $(DIST_DIR)/$(BINARY_NAME)-windows-amd64.exe $(MAIN_PATH)
@echo "Building for Linux amd64..."
@GOOS=linux GOARCH=amd64 $(GOBUILD) $(LDFLAGS) -o $(DIST_DIR)/$(BINARY_NAME)-linux-amd64 $(MAIN_PATH)
@echo "Building for Linux arm64..."
@GOOS=linux GOARCH=arm64 $(GOBUILD) $(LDFLAGS) -o $(DIST_DIR)/$(BINARY_NAME)-linux-arm64 $(MAIN_PATH)
@echo "Building for macOS amd64..."
@GOOS=darwin GOARCH=amd64 $(GOBUILD) $(LDFLAGS) -o $(DIST_DIR)/$(BINARY_NAME)-darwin-amd64 $(MAIN_PATH)
@echo "Building for macOS arm64..."
@GOOS=darwin GOARCH=arm64 $(GOBUILD) $(LDFLAGS) -o $(DIST_DIR)/$(BINARY_NAME)-darwin-arm64 $(MAIN_PATH)
@echo "All builds complete in $(DIST_DIR)/"
# 安装到系统
.PHONY: install
install: build
@echo "Installing $(BINARY_NAME)..."
$(GO) install $(LDFLAGS) $(MAIN_PATH)
@echo "Installed to $(shell go env GOPATH)/bin/$(BINARY_NAME)"
# 卸载
.PHONY: uninstall
uninstall:
@echo "Uninstalling $(BINARY_NAME)..."
@rm -f $(shell go env GOPATH)/bin/$(BINARY_NAME)$(shell go env GOEXE)
@echo "Uninstall complete"
# 帮助信息
.PHONY: help
help:
@echo "Available targets:"
@echo " make build - Build the binary"
@echo " make quick - Quick build without cleaning"
@echo " make run - Build and run the binary"
@echo " make dev - Run without building (using go run)"
@echo " make test - Run tests"
@echo " make coverage - Run tests with coverage report"
@echo " make clean - Clean build artifacts"
@echo " make deps - Download and tidy dependencies"
@echo " make deps-update - Update dependencies"
@echo " make lint - Run linter (requires golangci-lint)"
@echo " make fmt - Format code"
@echo " make vet - Run go vet"
@echo " make build-all - Build for multiple platforms"
@echo " make install - Install binary to GOPATH/bin"
@echo " make uninstall - Uninstall binary from GOPATH/bin"
@echo " make help - Show this help message"

92
cmd/nodeprobe/main.go Normal file
View File

@@ -0,0 +1,92 @@
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"time"
"github.com/meowrain/nodeprobe/internal/netstat"
)
func main() {
// 1. 获取初始数据
prevStats, err := netstat.GetInterfaceStats()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
if len(prevStats) == 0 {
fmt.Println("No network interfaces found")
os.Exit(1)
}
// 2. 设置刷新间隔(默认 1 秒)
interval := 1 * time.Second
// 3. 打印表头
printHeader()
// 4. 创建 ticker 定时器
ticker := time.NewTicker(interval)
defer ticker.Stop()
// 5. 监听 Ctrl+C 退出
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
// 6. 主循环
for {
select {
case <-ticker.C:
// 获取当前统计数据
currStats, err := netstat.GetInterfaceStats()
if err != nil {
continue
}
// 计算速率
rates := netstat.CalculateRate(prevStats, currStats, interval)
// 打印一行统计
printStats(rates, currStats)
// 更新上一次的数据
prevStats = currStats
case <-sigChan:
fmt.Println("\nExiting...")
return
}
}
}
func printHeader() {
fmt.Println("Network Interface Traffic Monitor")
fmt.Println("----------------------------------")
fmt.Printf("%-10s %15s %15s %15s %15s\n",
"Interface", "RX Rate", "TX Rate", "RX Total", "TX Total")
fmt.Println("----------------------------------")
}
func printStats(rates []netstat.TrafficRate, stats []netstat.InterfaceStats) {
// 创建统计 map
statsMap := make(map[string]netstat.InterfaceStats)
for _, s := range stats {
statsMap[s.Name] = s
}
for _, r := range rates {
s := statsMap[r.Name]
fmt.Printf("%-10s %15s %15s %15s %15s\n",
r.Name,
netstat.FormatBps(r.RxKbps*1000),
netstat.FormatBps(r.TxKbps*1000),
netstat.FormatBytes(float64(s.RxBytes)),
netstat.FormatBytes(float64(s.TxBytes)),
)
}
fmt.Println()
}

3
go.mod Normal file
View File

@@ -0,0 +1,3 @@
module github.com/meowrain/nodeprobe
go 1.25.5

134
internal/netstat/netstat.go Normal file
View File

@@ -0,0 +1,134 @@
package netstat
import (
"bufio"
"fmt"
"os"
"strings"
"time"
)
// InterfaceStats 网卡统计信息
type InterfaceStats struct {
Name string //网卡名称
RxBytes uint64 //接收字节数
TxBytes uint64 //发送字节数
RxPackets uint64 //接收数据包数
TxPackets uint64 //发送数据包数
RxErrors uint64 //接收错误数
TxErrors uint64 //发送错误数
}
type TrafficRate struct {
Name string // 网卡名称
RxBytesRate float64 // 接收字节速率
TxBytesRate float64 // 发送字节速率
RxKbps float64 // 接收比特速率
TxKbps float64 // 发送比特速率
}
// GetInterfaceStats 获取所有网卡的统计信息
func GetInterfaceStats() ([]InterfaceStats, error) {
// 打开/proc/net/dev文件
file, err := os.Open("/proc/net/dev")
if err != nil {
return nil, err
}
defer file.Close()
// 创建scanner逐行读取文件内容
scanner := bufio.NewScanner(file)
// 跳过前两行 头部信息
scanner.Scan()
scanner.Scan()
var stats []InterfaceStats
for scanner.Scan() {
line := scanner.Text()
// 按空格分割
fields := strings.Fields(line)
if len(fields) < 17 {
continue
}
// 提取网卡名
ifaceName := strings.TrimSuffix(fields[0], ":")
if ifaceName == "lo" {
continue //跳过回环接口
}
stat := InterfaceStats{
Name: ifaceName,
RxBytes: parseUint64(fields[1]),
RxPackets: parseUint64(fields[2]),
RxErrors: parseUint64(fields[3]),
TxBytes: parseUint64(fields[9]),
TxPackets: parseUint64(fields[10]),
TxErrors: parseUint64(fields[11]),
}
stats = append(stats, stat)
}
return stats, scanner.Err()
}
func parseUint64(s string) uint64 {
var val uint64
fmt.Sscanf(s, "%d", &val)
return val
}
func CalculateRate(prev, curr []InterfaceStats, duration time.Duration) []TrafficRate {
prevMap := make(map[string]InterfaceStats)
for _, stat := range prev {
prevMap[stat.Name] = stat
}
var rates []TrafficRate
seconds := duration.Seconds()
for _, c := range curr {
p, exists := prevMap[c.Name]
if !exists {
continue
}
rxBytesDiff := c.RxBytes - p.RxBytes
txBytesDiff := c.TxBytes - p.TxBytes
rate := TrafficRate{
Name: c.Name,
RxBytesRate: float64(rxBytesDiff) / seconds,
TxBytesRate: float64(txBytesDiff) / seconds,
RxKbps: (float64(rxBytesDiff) * 8) / (seconds * 1024),
TxKbps: (float64(txBytesDiff) * 8) / (seconds * 1024),
}
rates = append(rates, rate)
}
return rates
}
func FormatBytes(bytes float64) string {
const unit = 1024
if bytes < unit {
return fmt.Sprintf("%.2f B", bytes)
}
div, exp := uint64(unit), 0
for n := bytes / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.2f %cB", bytes/float64(div), "KMGTPE"[exp])
}
// FormatBps 格式化比特率为 1.23 Kbps、4.56 Mbps 等
func FormatBps(bps float64) string {
const unit = 1000
if bps < unit {
return fmt.Sprintf("%.2f bps", bps)
}
div, exp := float64(unit), 0
for n := bps / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.2f %cps", bps/div, "KMGTPE"[exp])
}

BIN
main.exe Normal file

Binary file not shown.