stats.go 3.8 KB

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