feat(server): 按用户精确统计流量 — agent 改读 v2ray_api StatsService
节点 sing-box 编入 with_v2ray_api 后,每个 dp_uuid 有独立计数器
user>>>{dp_uuid}>>>traffic>>>uplink|downlink。agent 用 QueryStats(reset=true)
取窗口 delta,按用户精确上报(替代旧 clash 节点总量分摊:单用户准、多用户近似)。
- render.go: experimental.v2ray_api(loopback :19091)+ stats.users 列全部 dp_uuid
- v2rayapi/: vendor sing-box stats.pb.go(消息)+ 手写 client(用 v2ray 规范
ServiceName 路径,绕开生成代码的 experimental.v2rayapi.* 误名)
- usage_v2ray.go: V2RayUsageSource,uplink/downlink 天然用户视角(无需 swap)
- agent.UseV2RayUsage() 取代 UseClashUsage();clash_api 保留作本地调试
节点需部署带 with_v2ray_api 的 sing-box(已编好 linux/amd64 二进制)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -38,8 +38,8 @@ func main() {
|
||||
}
|
||||
|
||||
agent := agentd.New(cfg, agentd.WithRestarter(agentd.SystemdRestarter{Unit: singboxUnit}))
|
||||
// 真实流量采集:读本地 clash_api 节点总量,按 dp_uuid 归属上报。
|
||||
agent.UseClashUsage()
|
||||
// 真实流量采集:读本地 v2ray_api StatsService 的 per-user 计数器,按 dp_uuid 精确上报。
|
||||
agent.UseV2RayUsage()
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
@@ -77,11 +77,16 @@ func New(cfg Config, opts ...Option) *Agent {
|
||||
// SingBox exposes the underlying manager (tests / introspection).
|
||||
func (a *Agent) SingBox() *SingBox { return a.sb }
|
||||
|
||||
// UseClashUsage wires the real usage source: reads node-total traffic from the
|
||||
// local clash_api and reports per-window deltas (attributed across dp_uuids).
|
||||
// Call after New (needs a.sb). Production main enables this; tests leave it off.
|
||||
// UseClashUsage wires the legacy usage source: reads node-total traffic from the
|
||||
// local clash_api and reports per-window deltas (split across dp_uuids — exact
|
||||
// for one active user, approximate for many). Superseded by UseV2RayUsage.
|
||||
func (a *Agent) UseClashUsage() { a.usage = newClashUsageSource(a.sb) }
|
||||
|
||||
// UseV2RayUsage wires the accurate usage source: reads PER-USER traffic from the
|
||||
// local v2ray_api StatsService (one counter pair per dp_uuid). Production main
|
||||
// enables this; tests leave it off.
|
||||
func (a *Agent) UseV2RayUsage() { a.usage = newV2RayUsageSource() }
|
||||
|
||||
// NodeUUID returns the enrolled node UUID (empty until Run enrolls).
|
||||
func (a *Agent) NodeUUID() string {
|
||||
v, _ := a.nodeUUID.Load().(string)
|
||||
|
||||
@@ -12,12 +12,15 @@ import (
|
||||
// derived password (DeriveHy2Password) — both from the same dp_uuid source.
|
||||
//
|
||||
// Only the opaque dp_uuid is ever written; no account identity touches the node.
|
||||
// clash_api endpoint(loopback only)供 agent 读取节点累计流量做用量统计。
|
||||
// v2ray_api 未编入该 sing-box 构建、clash /connections 又不暴露用户,故只能
|
||||
// 取节点总量(downloadTotal/uploadTotal),由 agent 按当前 dp_uuid 归属/分摊。
|
||||
//
|
||||
// 用量统计走 v2ray_api StatsService(loopback gRPC):节点 sing-box 编入
|
||||
// with_v2ray_api,stats.users 列出全部 dp_uuid → 每个用户独立的
|
||||
// user>>>{dp_uuid}>>>traffic>>>uplink|downlink 计数器,agent 按用户精确读取
|
||||
// (替代旧的 clash 节点总量分摊)。clash_api 仍保留作本地调试。
|
||||
const (
|
||||
clashAPIAddr = "127.0.0.1:19090"
|
||||
clashAPISecret = "pangolin-local-stats"
|
||||
v2rayAPIAddr = "127.0.0.1:19091"
|
||||
)
|
||||
|
||||
func renderSingboxConfig(creds []Cred, reality *agentv1.RealityInbound, hy2 *agentv1.Hy2Inbound, deriveKey string) ([]byte, error) {
|
||||
@@ -30,11 +33,35 @@ func renderSingboxConfig(creds []Cred, reality *agentv1.RealityInbound, hy2 *age
|
||||
"external_controller": clashAPIAddr,
|
||||
"secret": clashAPISecret,
|
||||
},
|
||||
"v2ray_api": map[string]any{
|
||||
"listen": v2rayAPIAddr,
|
||||
"stats": map[string]any{
|
||||
"enabled": true,
|
||||
"users": statsUsers(creds),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return json.MarshalIndent(cfg, "", " ")
|
||||
}
|
||||
|
||||
// statsUsers 收集所有去重 dp_uuid,供 v2ray_api stats.users 按用户开启流量计数器。
|
||||
func statsUsers(creds []Cred) []string {
|
||||
seen := make(map[string]struct{}, len(creds))
|
||||
users := make([]string, 0, len(creds))
|
||||
for _, c := range creds {
|
||||
if c.DpUUID == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[c.DpUUID]; ok {
|
||||
continue
|
||||
}
|
||||
seen[c.DpUUID] = struct{}{}
|
||||
users = append(users, c.DpUUID)
|
||||
}
|
||||
return users
|
||||
}
|
||||
|
||||
func buildInbounds(creds []Cred, reality *agentv1.RealityInbound, hy2 *agentv1.Hy2Inbound, deriveKey string) []any {
|
||||
inbounds := make([]any, 0, 2)
|
||||
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
package agentd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
agentv1 "github.com/wangjia/pangolin/server/internal/pb/agentv1"
|
||||
"github.com/wangjia/pangolin/server/internal/agentd/v2rayapi"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
// V2RayUsageSource reads PER-USER traffic from sing-box's v2ray_api StatsService
|
||||
// (loopback gRPC). Each provisioned dp_uuid has its own counters
|
||||
//
|
||||
// user>>>{dp_uuid}>>>traffic>>>uplink (bytes the user sent)
|
||||
// user>>>{dp_uuid}>>>traffic>>>downlink (bytes the user received)
|
||||
//
|
||||
// QueryStats with reset=true drains+zeroes the counters, so each call yields the
|
||||
// exact per-user delta for the elapsed window — accurate for any number of
|
||||
// concurrent users (unlike the old clash node-total split, see ClashUsageSource).
|
||||
//
|
||||
// uplink/downlink are already user-perspective (no swap needed). Only opaque
|
||||
// dp_uuids and byte counts are read — never identities/addresses/DNS.
|
||||
type V2RayUsageSource struct {
|
||||
addr string
|
||||
|
||||
mu sync.Mutex
|
||||
conn *grpc.ClientConn
|
||||
client *v2rayapi.StatsClient
|
||||
}
|
||||
|
||||
func newV2RayUsageSource() *V2RayUsageSource {
|
||||
return &V2RayUsageSource{addr: v2rayAPIAddr}
|
||||
}
|
||||
|
||||
// dial lazily establishes (and caches) the loopback gRPC connection.
|
||||
func (s *V2RayUsageSource) ensureClient() (*v2rayapi.StatsClient, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.client != nil {
|
||||
return s.client, nil
|
||||
}
|
||||
conn, err := grpc.NewClient(s.addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.conn = conn
|
||||
s.client = v2rayapi.NewStatsClient(conn)
|
||||
return s.client, nil
|
||||
}
|
||||
|
||||
// Collect drains per-user counters since the previous call.
|
||||
func (s *V2RayUsageSource) Collect() []*agentv1.UsageEntry {
|
||||
client, err := s.ensureClient()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
resp, err := client.QueryStats(ctx, &v2rayapi.QueryStatsRequest{
|
||||
Pattern: "user>>>",
|
||||
Reset_: true,
|
||||
})
|
||||
if err != nil || resp == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Aggregate uplink/downlink per dp_uuid.
|
||||
type acc struct{ up, down int64 }
|
||||
byUUID := make(map[string]*acc)
|
||||
for _, st := range resp.GetStat() {
|
||||
uuid, dir, ok := parseUserStat(st.GetName())
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
a := byUUID[uuid]
|
||||
if a == nil {
|
||||
a = &acc{}
|
||||
byUUID[uuid] = a
|
||||
}
|
||||
switch dir {
|
||||
case "uplink":
|
||||
a.up += st.GetValue()
|
||||
case "downlink":
|
||||
a.down += st.GetValue()
|
||||
}
|
||||
}
|
||||
|
||||
entries := make([]*agentv1.UsageEntry, 0, len(byUUID))
|
||||
for uuid, a := range byUUID {
|
||||
if a.up == 0 && a.down == 0 {
|
||||
continue
|
||||
}
|
||||
entries = append(entries, &agentv1.UsageEntry{
|
||||
DpUUID: uuid,
|
||||
BytesUp: a.up,
|
||||
BytesDown: a.down,
|
||||
SessionMinutes: 1, // 本窗口有流量 → 计 1 分钟在线(近似)
|
||||
})
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
// parseUserStat splits "user>>>{uuid}>>>traffic>>>{uplink|downlink}".
|
||||
func parseUserStat(name string) (uuid, dir string, ok bool) {
|
||||
parts := strings.Split(name, ">>>")
|
||||
if len(parts) != 4 || parts[0] != "user" || parts[2] != "traffic" {
|
||||
return "", "", false
|
||||
}
|
||||
return parts[1], parts[3], true
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package v2rayapi
|
||||
|
||||
// 极简 StatsService 客户端。消息定义(stats.pb.go)直接从 sing-box
|
||||
// experimental/v2rayapi 原样 vendor(wire 保证一致);此处只手写 QueryStats 调用。
|
||||
//
|
||||
// 注意:sing-box 服务端把 gRPC ServiceName 覆盖成了 v2ray 规范名
|
||||
// "v2ray.core.app.stats.command.StatsService"(见 sing-box stats.go),
|
||||
// 而生成代码里的常量是 "experimental.v2rayapi.StatsService"。直接抄生成的
|
||||
// client 会调错方法路径,故这里硬编码服务端真正注册的规范全名。
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
const queryStatsFullMethod = "/v2ray.core.app.stats.command.StatsService/QueryStats"
|
||||
|
||||
// StatsClient 是 v2ray StatsService 的最小客户端(只用 QueryStats)。
|
||||
type StatsClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
// NewStatsClient 包一个已建立的 gRPC 连接。
|
||||
func NewStatsClient(cc grpc.ClientConnInterface) *StatsClient {
|
||||
return &StatsClient{cc: cc}
|
||||
}
|
||||
|
||||
// QueryStats 按 pattern 取计数器;reset=true 时取值并清零(天然给出窗口 delta)。
|
||||
func (c *StatsClient) QueryStats(ctx context.Context, in *QueryStatsRequest, opts ...grpc.CallOption) (*QueryStatsResponse, error) {
|
||||
out := new(QueryStatsResponse)
|
||||
if err := c.cc.Invoke(ctx, queryStatsFullMethod, in, out, opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
@@ -0,0 +1,538 @@
|
||||
package v2rayapi
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type GetStatsRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Name of the stat counter.
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
// Whether or not to reset the counter to fetching its value.
|
||||
Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GetStatsRequest) Reset() {
|
||||
*x = GetStatsRequest{}
|
||||
mi := &file_experimental_v2rayapi_stats_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GetStatsRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetStatsRequest) ProtoMessage() {}
|
||||
|
||||
func (x *GetStatsRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_experimental_v2rayapi_stats_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GetStatsRequest.ProtoReflect.Descriptor instead.
|
||||
func (*GetStatsRequest) Descriptor() ([]byte, []int) {
|
||||
return file_experimental_v2rayapi_stats_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *GetStatsRequest) GetName() string {
|
||||
if x != nil {
|
||||
return x.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GetStatsRequest) GetReset_() bool {
|
||||
if x != nil {
|
||||
return x.Reset_
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type Stat struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Value int64 `protobuf:"varint,2,opt,name=value,proto3" json:"value,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Stat) Reset() {
|
||||
*x = Stat{}
|
||||
mi := &file_experimental_v2rayapi_stats_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Stat) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Stat) ProtoMessage() {}
|
||||
|
||||
func (x *Stat) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_experimental_v2rayapi_stats_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Stat.ProtoReflect.Descriptor instead.
|
||||
func (*Stat) Descriptor() ([]byte, []int) {
|
||||
return file_experimental_v2rayapi_stats_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *Stat) GetName() string {
|
||||
if x != nil {
|
||||
return x.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Stat) GetValue() int64 {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type GetStatsResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Stat *Stat `protobuf:"bytes,1,opt,name=stat,proto3" json:"stat,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GetStatsResponse) Reset() {
|
||||
*x = GetStatsResponse{}
|
||||
mi := &file_experimental_v2rayapi_stats_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GetStatsResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetStatsResponse) ProtoMessage() {}
|
||||
|
||||
func (x *GetStatsResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_experimental_v2rayapi_stats_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GetStatsResponse.ProtoReflect.Descriptor instead.
|
||||
func (*GetStatsResponse) Descriptor() ([]byte, []int) {
|
||||
return file_experimental_v2rayapi_stats_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *GetStatsResponse) GetStat() *Stat {
|
||||
if x != nil {
|
||||
return x.Stat
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type QueryStatsRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Deprecated, use Patterns instead
|
||||
Pattern string `protobuf:"bytes,1,opt,name=pattern,proto3" json:"pattern,omitempty"`
|
||||
Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"`
|
||||
Patterns []string `protobuf:"bytes,3,rep,name=patterns,proto3" json:"patterns,omitempty"`
|
||||
Regexp bool `protobuf:"varint,4,opt,name=regexp,proto3" json:"regexp,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *QueryStatsRequest) Reset() {
|
||||
*x = QueryStatsRequest{}
|
||||
mi := &file_experimental_v2rayapi_stats_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *QueryStatsRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*QueryStatsRequest) ProtoMessage() {}
|
||||
|
||||
func (x *QueryStatsRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_experimental_v2rayapi_stats_proto_msgTypes[3]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use QueryStatsRequest.ProtoReflect.Descriptor instead.
|
||||
func (*QueryStatsRequest) Descriptor() ([]byte, []int) {
|
||||
return file_experimental_v2rayapi_stats_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *QueryStatsRequest) GetPattern() string {
|
||||
if x != nil {
|
||||
return x.Pattern
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *QueryStatsRequest) GetReset_() bool {
|
||||
if x != nil {
|
||||
return x.Reset_
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *QueryStatsRequest) GetPatterns() []string {
|
||||
if x != nil {
|
||||
return x.Patterns
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *QueryStatsRequest) GetRegexp() bool {
|
||||
if x != nil {
|
||||
return x.Regexp
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type QueryStatsResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Stat []*Stat `protobuf:"bytes,1,rep,name=stat,proto3" json:"stat,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *QueryStatsResponse) Reset() {
|
||||
*x = QueryStatsResponse{}
|
||||
mi := &file_experimental_v2rayapi_stats_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *QueryStatsResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*QueryStatsResponse) ProtoMessage() {}
|
||||
|
||||
func (x *QueryStatsResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_experimental_v2rayapi_stats_proto_msgTypes[4]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use QueryStatsResponse.ProtoReflect.Descriptor instead.
|
||||
func (*QueryStatsResponse) Descriptor() ([]byte, []int) {
|
||||
return file_experimental_v2rayapi_stats_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *QueryStatsResponse) GetStat() []*Stat {
|
||||
if x != nil {
|
||||
return x.Stat
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SysStatsRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *SysStatsRequest) Reset() {
|
||||
*x = SysStatsRequest{}
|
||||
mi := &file_experimental_v2rayapi_stats_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *SysStatsRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SysStatsRequest) ProtoMessage() {}
|
||||
|
||||
func (x *SysStatsRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_experimental_v2rayapi_stats_proto_msgTypes[5]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use SysStatsRequest.ProtoReflect.Descriptor instead.
|
||||
func (*SysStatsRequest) Descriptor() ([]byte, []int) {
|
||||
return file_experimental_v2rayapi_stats_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
type SysStatsResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
NumGoroutine uint32 `protobuf:"varint,1,opt,name=NumGoroutine,proto3" json:"NumGoroutine,omitempty"`
|
||||
NumGC uint32 `protobuf:"varint,2,opt,name=NumGC,proto3" json:"NumGC,omitempty"`
|
||||
Alloc uint64 `protobuf:"varint,3,opt,name=Alloc,proto3" json:"Alloc,omitempty"`
|
||||
TotalAlloc uint64 `protobuf:"varint,4,opt,name=TotalAlloc,proto3" json:"TotalAlloc,omitempty"`
|
||||
Sys uint64 `protobuf:"varint,5,opt,name=Sys,proto3" json:"Sys,omitempty"`
|
||||
Mallocs uint64 `protobuf:"varint,6,opt,name=Mallocs,proto3" json:"Mallocs,omitempty"`
|
||||
Frees uint64 `protobuf:"varint,7,opt,name=Frees,proto3" json:"Frees,omitempty"`
|
||||
LiveObjects uint64 `protobuf:"varint,8,opt,name=LiveObjects,proto3" json:"LiveObjects,omitempty"`
|
||||
PauseTotalNs uint64 `protobuf:"varint,9,opt,name=PauseTotalNs,proto3" json:"PauseTotalNs,omitempty"`
|
||||
Uptime uint32 `protobuf:"varint,10,opt,name=Uptime,proto3" json:"Uptime,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *SysStatsResponse) Reset() {
|
||||
*x = SysStatsResponse{}
|
||||
mi := &file_experimental_v2rayapi_stats_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *SysStatsResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SysStatsResponse) ProtoMessage() {}
|
||||
|
||||
func (x *SysStatsResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_experimental_v2rayapi_stats_proto_msgTypes[6]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use SysStatsResponse.ProtoReflect.Descriptor instead.
|
||||
func (*SysStatsResponse) Descriptor() ([]byte, []int) {
|
||||
return file_experimental_v2rayapi_stats_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
func (x *SysStatsResponse) GetNumGoroutine() uint32 {
|
||||
if x != nil {
|
||||
return x.NumGoroutine
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *SysStatsResponse) GetNumGC() uint32 {
|
||||
if x != nil {
|
||||
return x.NumGC
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *SysStatsResponse) GetAlloc() uint64 {
|
||||
if x != nil {
|
||||
return x.Alloc
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *SysStatsResponse) GetTotalAlloc() uint64 {
|
||||
if x != nil {
|
||||
return x.TotalAlloc
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *SysStatsResponse) GetSys() uint64 {
|
||||
if x != nil {
|
||||
return x.Sys
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *SysStatsResponse) GetMallocs() uint64 {
|
||||
if x != nil {
|
||||
return x.Mallocs
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *SysStatsResponse) GetFrees() uint64 {
|
||||
if x != nil {
|
||||
return x.Frees
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *SysStatsResponse) GetLiveObjects() uint64 {
|
||||
if x != nil {
|
||||
return x.LiveObjects
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *SysStatsResponse) GetPauseTotalNs() uint64 {
|
||||
if x != nil {
|
||||
return x.PauseTotalNs
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *SysStatsResponse) GetUptime() uint32 {
|
||||
if x != nil {
|
||||
return x.Uptime
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var File_experimental_v2rayapi_stats_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_experimental_v2rayapi_stats_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"!experimental/v2rayapi/stats.proto\x12\x15experimental.v2rayapi\";\n" +
|
||||
"\x0fGetStatsRequest\x12\x12\n" +
|
||||
"\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" +
|
||||
"\x05reset\x18\x02 \x01(\bR\x05reset\"0\n" +
|
||||
"\x04Stat\x12\x12\n" +
|
||||
"\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\x03R\x05value\"C\n" +
|
||||
"\x10GetStatsResponse\x12/\n" +
|
||||
"\x04stat\x18\x01 \x01(\v2\x1b.experimental.v2rayapi.StatR\x04stat\"w\n" +
|
||||
"\x11QueryStatsRequest\x12\x18\n" +
|
||||
"\apattern\x18\x01 \x01(\tR\apattern\x12\x14\n" +
|
||||
"\x05reset\x18\x02 \x01(\bR\x05reset\x12\x1a\n" +
|
||||
"\bpatterns\x18\x03 \x03(\tR\bpatterns\x12\x16\n" +
|
||||
"\x06regexp\x18\x04 \x01(\bR\x06regexp\"E\n" +
|
||||
"\x12QueryStatsResponse\x12/\n" +
|
||||
"\x04stat\x18\x01 \x03(\v2\x1b.experimental.v2rayapi.StatR\x04stat\"\x11\n" +
|
||||
"\x0fSysStatsRequest\"\xa2\x02\n" +
|
||||
"\x10SysStatsResponse\x12\"\n" +
|
||||
"\fNumGoroutine\x18\x01 \x01(\rR\fNumGoroutine\x12\x14\n" +
|
||||
"\x05NumGC\x18\x02 \x01(\rR\x05NumGC\x12\x14\n" +
|
||||
"\x05Alloc\x18\x03 \x01(\x04R\x05Alloc\x12\x1e\n" +
|
||||
"\n" +
|
||||
"TotalAlloc\x18\x04 \x01(\x04R\n" +
|
||||
"TotalAlloc\x12\x10\n" +
|
||||
"\x03Sys\x18\x05 \x01(\x04R\x03Sys\x12\x18\n" +
|
||||
"\aMallocs\x18\x06 \x01(\x04R\aMallocs\x12\x14\n" +
|
||||
"\x05Frees\x18\a \x01(\x04R\x05Frees\x12 \n" +
|
||||
"\vLiveObjects\x18\b \x01(\x04R\vLiveObjects\x12\"\n" +
|
||||
"\fPauseTotalNs\x18\t \x01(\x04R\fPauseTotalNs\x12\x16\n" +
|
||||
"\x06Uptime\x18\n" +
|
||||
" \x01(\rR\x06Uptime2\xb4\x02\n" +
|
||||
"\fStatsService\x12]\n" +
|
||||
"\bGetStats\x12&.experimental.v2rayapi.GetStatsRequest\x1a'.experimental.v2rayapi.GetStatsResponse\"\x00\x12c\n" +
|
||||
"\n" +
|
||||
"QueryStats\x12(.experimental.v2rayapi.QueryStatsRequest\x1a).experimental.v2rayapi.QueryStatsResponse\"\x00\x12`\n" +
|
||||
"\vGetSysStats\x12&.experimental.v2rayapi.SysStatsRequest\x1a'.experimental.v2rayapi.SysStatsResponse\"\x00B4Z2github.com/sagernet/sing-box/experimental/v2rayapib\x06proto3"
|
||||
|
||||
var (
|
||||
file_experimental_v2rayapi_stats_proto_rawDescOnce sync.Once
|
||||
file_experimental_v2rayapi_stats_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_experimental_v2rayapi_stats_proto_rawDescGZIP() []byte {
|
||||
file_experimental_v2rayapi_stats_proto_rawDescOnce.Do(func() {
|
||||
file_experimental_v2rayapi_stats_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_experimental_v2rayapi_stats_proto_rawDesc), len(file_experimental_v2rayapi_stats_proto_rawDesc)))
|
||||
})
|
||||
return file_experimental_v2rayapi_stats_proto_rawDescData
|
||||
}
|
||||
|
||||
var (
|
||||
file_experimental_v2rayapi_stats_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
|
||||
file_experimental_v2rayapi_stats_proto_goTypes = []any{
|
||||
(*GetStatsRequest)(nil), // 0: experimental.v2rayapi.GetStatsRequest
|
||||
(*Stat)(nil), // 1: experimental.v2rayapi.Stat
|
||||
(*GetStatsResponse)(nil), // 2: experimental.v2rayapi.GetStatsResponse
|
||||
(*QueryStatsRequest)(nil), // 3: experimental.v2rayapi.QueryStatsRequest
|
||||
(*QueryStatsResponse)(nil), // 4: experimental.v2rayapi.QueryStatsResponse
|
||||
(*SysStatsRequest)(nil), // 5: experimental.v2rayapi.SysStatsRequest
|
||||
(*SysStatsResponse)(nil), // 6: experimental.v2rayapi.SysStatsResponse
|
||||
}
|
||||
)
|
||||
|
||||
var file_experimental_v2rayapi_stats_proto_depIdxs = []int32{
|
||||
1, // 0: experimental.v2rayapi.GetStatsResponse.stat:type_name -> experimental.v2rayapi.Stat
|
||||
1, // 1: experimental.v2rayapi.QueryStatsResponse.stat:type_name -> experimental.v2rayapi.Stat
|
||||
0, // 2: experimental.v2rayapi.StatsService.GetStats:input_type -> experimental.v2rayapi.GetStatsRequest
|
||||
3, // 3: experimental.v2rayapi.StatsService.QueryStats:input_type -> experimental.v2rayapi.QueryStatsRequest
|
||||
5, // 4: experimental.v2rayapi.StatsService.GetSysStats:input_type -> experimental.v2rayapi.SysStatsRequest
|
||||
2, // 5: experimental.v2rayapi.StatsService.GetStats:output_type -> experimental.v2rayapi.GetStatsResponse
|
||||
4, // 6: experimental.v2rayapi.StatsService.QueryStats:output_type -> experimental.v2rayapi.QueryStatsResponse
|
||||
6, // 7: experimental.v2rayapi.StatsService.GetSysStats:output_type -> experimental.v2rayapi.SysStatsResponse
|
||||
5, // [5:8] is the sub-list for method output_type
|
||||
2, // [2:5] is the sub-list for method input_type
|
||||
2, // [2:2] is the sub-list for extension type_name
|
||||
2, // [2:2] is the sub-list for extension extendee
|
||||
0, // [0:2] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_experimental_v2rayapi_stats_proto_init() }
|
||||
func file_experimental_v2rayapi_stats_proto_init() {
|
||||
if File_experimental_v2rayapi_stats_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_experimental_v2rayapi_stats_proto_rawDesc), len(file_experimental_v2rayapi_stats_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 7,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_experimental_v2rayapi_stats_proto_goTypes,
|
||||
DependencyIndexes: file_experimental_v2rayapi_stats_proto_depIdxs,
|
||||
MessageInfos: file_experimental_v2rayapi_stats_proto_msgTypes,
|
||||
}.Build()
|
||||
File_experimental_v2rayapi_stats_proto = out.File
|
||||
file_experimental_v2rayapi_stats_proto_goTypes = nil
|
||||
file_experimental_v2rayapi_stats_proto_depIdxs = nil
|
||||
}
|
||||
Reference in New Issue
Block a user