Files
NexusGate/agent/stats-query.go
T
aaaamoooo d9bf21a847
CI / test (push) Waiting to run
CI / anytls-smoke (push) Waiting to run
agent: default sing-box stats API port 10086 -> 10089
Avoids the v2ray_api listen colliding with L-UI's fixed 10086 on
dual-homed nodes. NG_SINGBOX_API_PORT env still overrides. Aligns the
stats-query --server default and the doctor script fallback to the
same value. Agent version stays 0.6.8.
2026-10-23 12:33:00 +00: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:10089", "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)
}