stats.go 3.7 KB

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