feat: 新增 sys.uptime/net.iface/net.conn/net.speed/proc.tree/file.empty/user.info 命令
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
package executor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// runNetSpeed implements `kopsc net speed`: simple network speed test.
|
||||
func runNetSpeed(ctx context.Context, req BuiltinRequest) error {
|
||||
opts, err := parseNetSpeedArgs(req.Args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprint(req.Stdout, "=== 网络速度测试 ===\n")
|
||||
|
||||
// Download test
|
||||
if !opts.uploadOnly {
|
||||
fmt.Fprintln(req.Stdout, "测试下载速度...")
|
||||
downloadSpeed, err := testDownload(ctx, opts.size)
|
||||
if err != nil {
|
||||
fmt.Fprintf(req.Stderr, "下载测试失败: %v\n", err)
|
||||
} else {
|
||||
fmt.Fprintf(req.Stdout, "下载速度: %s/s\n", humanSize(downloadSpeed))
|
||||
}
|
||||
fmt.Fprintln(req.Stdout)
|
||||
}
|
||||
|
||||
// Upload test
|
||||
if !opts.downloadOnly {
|
||||
fmt.Fprintln(req.Stdout, "测试上传速度...")
|
||||
uploadSpeed, err := testUpload(ctx, opts.size)
|
||||
if err != nil {
|
||||
fmt.Fprintf(req.Stderr, "上传测试失败: %v\n", err)
|
||||
} else {
|
||||
fmt.Fprintf(req.Stdout, "上传速度: %s/s\n", humanSize(uploadSpeed))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type netSpeedOptions struct {
|
||||
size int64
|
||||
downloadOnly bool
|
||||
uploadOnly bool
|
||||
}
|
||||
|
||||
func parseNetSpeedArgs(args []string) (netSpeedOptions, error) {
|
||||
opts := netSpeedOptions{size: 10 * 1024 * 1024} // 10MB default
|
||||
|
||||
for i := 0; i < len(args); i++ {
|
||||
arg := args[i]
|
||||
switch {
|
||||
case arg == "--":
|
||||
remaining := args[i+1:]
|
||||
if len(remaining) > 0 {
|
||||
n, err := parseSizeArg(remaining[0])
|
||||
if err != nil {
|
||||
return opts, fmt.Errorf("大小参数无效: %w", err)
|
||||
}
|
||||
opts.size = n
|
||||
}
|
||||
i = len(args)
|
||||
case arg == "--size" || arg == "-s":
|
||||
if i+1 >= len(args) {
|
||||
return opts, fmt.Errorf("%s 缺少大小参数", arg)
|
||||
}
|
||||
i++
|
||||
n, err := parseSizeArg(args[i])
|
||||
if err != nil {
|
||||
return opts, fmt.Errorf("--size: %w", err)
|
||||
}
|
||||
opts.size = n
|
||||
case strings.HasPrefix(arg, "--size="):
|
||||
n, err := parseSizeArg(strings.TrimPrefix(arg, "--size="))
|
||||
if err != nil {
|
||||
return opts, fmt.Errorf("--size: %w", err)
|
||||
}
|
||||
opts.size = n
|
||||
case arg == "--download" || arg == "-d":
|
||||
opts.downloadOnly = true
|
||||
case arg == "--upload" || arg == "-u":
|
||||
opts.uploadOnly = true
|
||||
default:
|
||||
return opts, fmt.Errorf("未知选项 %q", arg)
|
||||
}
|
||||
}
|
||||
|
||||
if opts.downloadOnly && opts.uploadOnly {
|
||||
return opts, fmt.Errorf("--download 和 --upload 不能同时使用")
|
||||
}
|
||||
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
func testDownload(ctx context.Context, size int64) (int64, error) {
|
||||
// Use Cloudflare's speed test endpoint
|
||||
url := fmt.Sprintf("https://speed.cloudflare.com/__down?bytes=%d", size)
|
||||
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return 0, fmt.Errorf("HTTP %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
n, err := io.Copy(io.Discard, resp.Body)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
elapsed := time.Since(start).Seconds()
|
||||
if elapsed == 0 {
|
||||
elapsed = 0.001
|
||||
}
|
||||
return int64(float64(n) / elapsed), nil
|
||||
}
|
||||
|
||||
func testUpload(ctx context.Context, size int64) (int64, error) {
|
||||
// Use httpbin.org for upload test
|
||||
url := "https://httpbin.org/post"
|
||||
|
||||
// Create a reader that generates data on the fly
|
||||
data := make([]byte, size)
|
||||
for i := range data {
|
||||
data[i] = 'A'
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", url, strings.NewReader(string(data)))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/octet-stream")
|
||||
|
||||
start := time.Now()
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return 0, fmt.Errorf("HTTP %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
elapsed := time.Since(start).Seconds()
|
||||
if elapsed == 0 {
|
||||
elapsed = 0.001
|
||||
}
|
||||
return int64(float64(size) / elapsed), nil
|
||||
}
|
||||
Reference in New Issue
Block a user