Files
NexusGate/agent/stats-query.go
Muse 084afeb8f4 3x-ui 式共享节点改造:nodes 状态机 + nodeClients 各自凭据
- 移除 VMess 协议
- VLESS/SS2022 按用户流量统计(AnyTLS 日志解析,SS 节点级)
- sing-box v2ray_api stats 支持
- 内存优化:面板 128M,agent 64M,sing-box 64M
2026-10-19 22:23:40 +08:00

45 lines
1.5 KiB
Go

// Command stats-query reads sing-box's V2Ray API using sing-box's own protobuf schema.
// Xray's CLI cannot query this API: the two engines use different gRPC service names.
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"time"
"github.com/sagernet/sing-box/experimental/v2rayapi"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
server := flag.String("server", "127.0.0.1:10086", "sing-box statistics API address")
flag.Parse()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
conn, err := grpc.NewClient(*server, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil { fail(err) }
defer conn.Close()
var response v2rayapi.QueryStatsResponse
// Query both inbound-level and user-level stats (sing-box requires users
// to be explicitly listed in the v2ray_api stats config).
err = conn.Invoke(ctx, "/v2ray.core.app.stats.command.StatsService/QueryStats",
&v2rayapi.QueryStatsRequest{Patterns: []string{"inbound>>>", "user>>>"}}, &response)
if err != nil { fail(err) }
result := struct { Stat []struct { Name string `json:"name"`; Value string `json:"value"` } `json:"stat"` }{}
for _, stat := range response.Stat {
result.Stat = append(result.Stat, struct { Name string `json:"name"`; Value string `json:"value"` }{
Name: stat.Name, Value: fmt.Sprint(stat.Value),
})
}
if err := json.NewEncoder(os.Stdout).Encode(result); err != nil { fail(err) }
}
func fail(err error) {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}