Hunter0x7c7
2022-08-11 b8230139fb40edea387617b6accd8371e37eda58
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package api
 
import (
    "fmt"
    "os"
    "sort"
    "strings"
    "time"
 
    statsService "github.com/v2fly/v2ray-core/v5/app/stats/command"
    "github.com/v2fly/v2ray-core/v5/common/units"
    "github.com/v2fly/v2ray-core/v5/main/commands/base"
)
 
var cmdStats = &base.Command{
    CustomFlags: true,
    UsageLine:   "{{.Exec}} api stats [--server=127.0.0.1:8080] [pattern]...",
    Short:       "query statistics",
    Long: `
Query statistics from V2Ray.
 
> Make sure you have "StatsService" set in "config.api.services" 
of server config.
 
Arguments:
 
    -regexp
        The patterns are using regexp.
 
    -reset
        Reset counters to 0 after fetching their values.
 
    -runtime
        Get runtime statistics.
 
    -json
        Use json output.
 
    -s, -server <server:port>
        The API server address. Default 127.0.0.1:8080
 
    -t, -timeout <seconds>
        Timeout seconds to call API. Default 3
 
Example:
 
    {{.Exec}} {{.LongName}} -runtime
    {{.Exec}} {{.LongName}} node1
    {{.Exec}} {{.LongName}} -json node1 node2
    {{.Exec}} {{.LongName}} -regexp 'node1.+downlink'
`,
    Run: executeStats,
}
 
func executeStats(cmd *base.Command, args []string) {
    setSharedFlags(cmd)
    var (
        runtime bool
        regexp  bool
        reset   bool
    )
    cmd.Flag.BoolVar(&runtime, "runtime", false, "")
    cmd.Flag.BoolVar(&regexp, "regexp", false, "")
    cmd.Flag.BoolVar(&reset, "reset", false, "")
    cmd.Flag.Parse(args)
    unnamed := cmd.Flag.Args()
    if runtime {
        getRuntimeStats(apiJSON)
        return
    }
    getStats(unnamed, regexp, reset, apiJSON)
}
 
func getRuntimeStats(jsonOutput bool) {
    conn, ctx, close := dialAPIServer()
    defer close()
 
    client := statsService.NewStatsServiceClient(conn)
    r := &statsService.SysStatsRequest{}
    resp, err := client.GetSysStats(ctx, r)
    if err != nil {
        base.Fatalf("failed to get sys stats: %s", err)
    }
    if jsonOutput {
        showJSONResponse(resp)
        return
    }
    showRuntimeStats(resp)
}
 
func showRuntimeStats(s *statsService.SysStatsResponse) {
    formats := []string{"%-22s", "%-10s"}
    rows := [][]string{
        {"Up time", (time.Duration(s.Uptime) * time.Second).String()},
        {"Memory obtained", units.ByteSize(s.Sys).String()},
        {"Number of goroutines", fmt.Sprintf("%d", s.NumGoroutine)},
        {"Heap allocated", units.ByteSize(s.Alloc).String()},
        {"Live objects", fmt.Sprintf("%d", s.LiveObjects)},
        {"Heap allocated total", units.ByteSize(s.TotalAlloc).String()},
        {"Heap allocate count", fmt.Sprintf("%d", s.Mallocs)},
        {"Heap free count", fmt.Sprintf("%d", s.Frees)},
        {"Number of GC", fmt.Sprintf("%d", s.NumGC)},
        {"Time of GC pause", (time.Duration(s.PauseTotalNs) * time.Nanosecond).String()},
    }
    sb := new(strings.Builder)
    writeRow(sb, 0, 0,
        []string{"Item", "Value"},
        formats,
    )
    for i, r := range rows {
        writeRow(sb, 0, i+1, r, formats)
    }
    os.Stdout.WriteString(sb.String())
}
 
func getStats(patterns []string, regexp, reset, jsonOutput bool) {
    conn, ctx, close := dialAPIServer()
    defer close()
 
    client := statsService.NewStatsServiceClient(conn)
    r := &statsService.QueryStatsRequest{
        Patterns: patterns,
        Regexp:   regexp,
        Reset_:   reset,
    }
    resp, err := client.QueryStats(ctx, r)
    if err != nil {
        base.Fatalf("failed to query stats: %s", err)
    }
    if jsonOutput {
        showJSONResponse(resp)
        return
    }
    sort.Slice(resp.Stat, func(i, j int) bool {
        return resp.Stat[i].Name < resp.Stat[j].Name
    })
    showStats(resp.Stat)
}
 
func showStats(stats []*statsService.Stat) {
    if len(stats) == 0 {
        return
    }
    formats := []string{"%-12s", "%s"}
    sum := int64(0)
    sb := new(strings.Builder)
    idx := 0
    writeRow(sb, 0, 0,
        []string{"Value", "Name"},
        formats,
    )
    for _, stat := range stats {
        // if stat.Value == 0 {
        //     continue
        // }
        idx++
        sum += stat.Value
        writeRow(
            sb, 0, idx,
            []string{units.ByteSize(stat.Value).String(), stat.Name},
            formats,
        )
    }
    sb.WriteString(
        fmt.Sprintf("\nTotal: %s\n", units.ByteSize(sum)),
    )
    os.Stdout.WriteString(sb.String())
}