From 64a1a64c3f10a268341b2811c7be391db153bd3a Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Fri, 19 Jun 2026 10:18:33 +0800 Subject: [PATCH] =?UTF-8?q?feat(server):=20=E6=8C=89=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E7=B2=BE=E7=A1=AE=E7=BB=9F=E8=AE=A1=E6=B5=81=E9=87=8F=20?= =?UTF-8?q?=E2=80=94=20agent=20=E6=94=B9=E8=AF=BB=20v2ray=5Fapi=20StatsSer?= =?UTF-8?q?vice?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 节点 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 --- server/cmd/agent/main.go | 4 +- server/internal/agentd/agent.go | 11 +- server/internal/agentd/render.go | 33 +- server/internal/agentd/usage_v2ray.go | 115 +++++ server/internal/agentd/v2rayapi/client.go | 36 ++ server/internal/agentd/v2rayapi/stats.pb.go | 538 ++++++++++++++++++++ 6 files changed, 729 insertions(+), 8 deletions(-) create mode 100644 server/internal/agentd/usage_v2ray.go create mode 100644 server/internal/agentd/v2rayapi/client.go create mode 100644 server/internal/agentd/v2rayapi/stats.pb.go diff --git a/server/cmd/agent/main.go b/server/cmd/agent/main.go index c01a75e..8b24693 100644 --- a/server/cmd/agent/main.go +++ b/server/cmd/agent/main.go @@ -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() diff --git a/server/internal/agentd/agent.go b/server/internal/agentd/agent.go index 98871e6..aedbcec 100644 --- a/server/internal/agentd/agent.go +++ b/server/internal/agentd/agent.go @@ -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) diff --git a/server/internal/agentd/render.go b/server/internal/agentd/render.go index 1ff82a0..239e08b 100644 --- a/server/internal/agentd/render.go +++ b/server/internal/agentd/render.go @@ -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) diff --git a/server/internal/agentd/usage_v2ray.go b/server/internal/agentd/usage_v2ray.go new file mode 100644 index 0000000..21457db --- /dev/null +++ b/server/internal/agentd/usage_v2ray.go @@ -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 +} diff --git a/server/internal/agentd/v2rayapi/client.go b/server/internal/agentd/v2rayapi/client.go new file mode 100644 index 0000000..588d0a3 --- /dev/null +++ b/server/internal/agentd/v2rayapi/client.go @@ -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 +} diff --git a/server/internal/agentd/v2rayapi/stats.pb.go b/server/internal/agentd/v2rayapi/stats.pb.go new file mode 100644 index 0000000..586b9a7 --- /dev/null +++ b/server/internal/agentd/v2rayapi/stats.pb.go @@ -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 +}