command_volume_list.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. package shell
  2. import (
  3. "bytes"
  4. "flag"
  5. "fmt"
  6. "path/filepath"
  7. "slices"
  8. "sort"
  9. "strings"
  10. "time"
  11. "github.com/seaweedfs/seaweedfs/weed/pb/master_pb"
  12. "github.com/seaweedfs/seaweedfs/weed/storage/erasure_coding"
  13. "github.com/seaweedfs/seaweedfs/weed/storage/types"
  14. "github.com/seaweedfs/seaweedfs/weed/util"
  15. "io"
  16. )
  17. func init() {
  18. Commands = append(Commands, &commandVolumeList{})
  19. }
  20. type commandVolumeList struct {
  21. collectionPattern *string
  22. dataCenter *string
  23. rack *string
  24. dataNode *string
  25. readonly *bool
  26. writable *bool
  27. volumeId *uint64
  28. volumeSizeLimitMb uint64
  29. }
  30. func (c *commandVolumeList) Name() string {
  31. return "volume.list"
  32. }
  33. func (c *commandVolumeList) Help() string {
  34. return `list all volumes
  35. This command list all volumes as a tree of dataCenter > rack > dataNode > volume.
  36. `
  37. }
  38. func (c *commandVolumeList) HasTag(CommandTag) bool {
  39. return false
  40. }
  41. func (c *commandVolumeList) Do(args []string, commandEnv *CommandEnv, writer io.Writer) (err error) {
  42. volumeListCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
  43. verbosityLevel := volumeListCommand.Int("v", 5, "verbose mode: 0, 1, 2, 3, 4, 5")
  44. c.collectionPattern = volumeListCommand.String("collectionPattern", "", "match with wildcard characters '*' and '?'")
  45. c.readonly = volumeListCommand.Bool("readonly", false, "show only readonly volumes")
  46. c.writable = volumeListCommand.Bool("writable", false, "show only writable volumes")
  47. c.volumeId = volumeListCommand.Uint64("volumeId", 0, "show only volume id")
  48. c.dataCenter = volumeListCommand.String("dataCenter", "", "show volumes only from the specified data center")
  49. c.rack = volumeListCommand.String("rack", "", "show volumes only from the specified rack")
  50. c.dataNode = volumeListCommand.String("dataNode", "", "show volumes only from the specified data node")
  51. if err = volumeListCommand.Parse(args); err != nil {
  52. return nil
  53. }
  54. // collect topology information
  55. var topologyInfo *master_pb.TopologyInfo
  56. topologyInfo, c.volumeSizeLimitMb, err = collectTopologyInfo(commandEnv, 0)
  57. if err != nil {
  58. return err
  59. }
  60. c.writeTopologyInfo(writer, topologyInfo, c.volumeSizeLimitMb, *verbosityLevel)
  61. return nil
  62. }
  63. func sortMapKey[T1 comparable, T2 any](m map[T1]T2) []T1 {
  64. keys := make([]T1, 0, len(m))
  65. for k, _ := range m {
  66. keys = append(keys, k)
  67. }
  68. sort.Slice(keys, func(i, j int) bool {
  69. switch v1 := any(keys[i]).(type) {
  70. case int:
  71. return v1 < any(keys[j]).(int)
  72. case string:
  73. if strings.Compare(v1, any(keys[j]).(string)) < 0 {
  74. return true
  75. }
  76. return false
  77. default:
  78. return false
  79. }
  80. })
  81. return keys
  82. }
  83. func diskInfosToString(diskInfos map[string]*master_pb.DiskInfo) string {
  84. var buf bytes.Buffer
  85. for _, diskType := range sortMapKey(diskInfos) {
  86. diskInfo := diskInfos[diskType]
  87. if diskType == "" {
  88. diskType = types.HddType
  89. }
  90. fmt.Fprintf(&buf, " %s(volume:%d/%d active:%d free:%d remote:%d)", diskType, diskInfo.VolumeCount, diskInfo.MaxVolumeCount, diskInfo.ActiveVolumeCount, diskInfo.FreeVolumeCount, diskInfo.RemoteVolumeCount)
  91. }
  92. return buf.String()
  93. }
  94. func diskInfoToString(diskInfo *master_pb.DiskInfo) string {
  95. var buf bytes.Buffer
  96. fmt.Fprintf(&buf, "volume:%d/%d active:%d free:%d remote:%d", diskInfo.VolumeCount, diskInfo.MaxVolumeCount, diskInfo.ActiveVolumeCount, diskInfo.FreeVolumeCount, diskInfo.RemoteVolumeCount)
  97. return buf.String()
  98. }
  99. func (c *commandVolumeList) writeTopologyInfo(writer io.Writer, t *master_pb.TopologyInfo, volumeSizeLimitMb uint64, verbosityLevel int) statistics {
  100. output(verbosityLevel >= 0, writer, "Topology volumeSizeLimit:%d MB%s\n", volumeSizeLimitMb, diskInfosToString(t.DiskInfos))
  101. slices.SortFunc(t.DataCenterInfos, func(a, b *master_pb.DataCenterInfo) int {
  102. return strings.Compare(a.Id, b.Id)
  103. })
  104. var s statistics
  105. for _, dc := range t.DataCenterInfos {
  106. if *c.dataCenter != "" && *c.dataCenter != dc.Id {
  107. continue
  108. }
  109. s = s.plus(c.writeDataCenterInfo(writer, dc, verbosityLevel))
  110. }
  111. output(verbosityLevel >= 0, writer, "%+v \n", s)
  112. return s
  113. }
  114. func (c *commandVolumeList) writeDataCenterInfo(writer io.Writer, t *master_pb.DataCenterInfo, verbosityLevel int) statistics {
  115. var s statistics
  116. slices.SortFunc(t.RackInfos, func(a, b *master_pb.RackInfo) int {
  117. return strings.Compare(a.Id, b.Id)
  118. })
  119. dataCenterInfoFound := false
  120. for _, r := range t.RackInfos {
  121. if *c.rack != "" && *c.rack != r.Id {
  122. continue
  123. }
  124. s = s.plus(c.writeRackInfo(writer, r, verbosityLevel, func() {
  125. output(verbosityLevel >= 1, writer, " DataCenter %s%s\n", t.Id, diskInfosToString(t.DiskInfos))
  126. }))
  127. if !dataCenterInfoFound && !s.isEmpty() {
  128. dataCenterInfoFound = true
  129. }
  130. }
  131. output(dataCenterInfoFound && verbosityLevel >= 1, writer, " DataCenter %s %+v \n", t.Id, s)
  132. return s
  133. }
  134. func (c *commandVolumeList) writeRackInfo(writer io.Writer, t *master_pb.RackInfo, verbosityLevel int, outCenterInfo func()) statistics {
  135. var s statistics
  136. slices.SortFunc(t.DataNodeInfos, func(a, b *master_pb.DataNodeInfo) int {
  137. return strings.Compare(a.Id, b.Id)
  138. })
  139. rackInfoFound := false
  140. for _, dn := range t.DataNodeInfos {
  141. if *c.dataNode != "" && *c.dataNode != dn.Id {
  142. continue
  143. }
  144. s = s.plus(c.writeDataNodeInfo(writer, dn, verbosityLevel, func() {
  145. outCenterInfo()
  146. output(verbosityLevel >= 2, writer, " Rack %s%s\n", t.Id, diskInfosToString(t.DiskInfos))
  147. }))
  148. if !rackInfoFound && !s.isEmpty() {
  149. rackInfoFound = true
  150. }
  151. }
  152. output(rackInfoFound && verbosityLevel >= 2, writer, " Rack %s %+v \n", t.Id, s)
  153. return s
  154. }
  155. func (c *commandVolumeList) writeDataNodeInfo(writer io.Writer, t *master_pb.DataNodeInfo, verbosityLevel int, outRackInfo func()) statistics {
  156. var s statistics
  157. diskInfoFound := false
  158. for _, diskType := range sortMapKey(t.DiskInfos) {
  159. diskInfo := t.DiskInfos[diskType]
  160. s = s.plus(c.writeDiskInfo(writer, diskInfo, verbosityLevel, func() {
  161. outRackInfo()
  162. output(verbosityLevel >= 3, writer, " DataNode %s%s\n", t.Id, diskInfosToString(t.DiskInfos))
  163. }))
  164. if !diskInfoFound && !s.isEmpty() {
  165. diskInfoFound = true
  166. }
  167. }
  168. output(diskInfoFound && verbosityLevel >= 3, writer, " DataNode %s %+v \n", t.Id, s)
  169. return s
  170. }
  171. func (c *commandVolumeList) isNotMatchDiskInfo(readOnly bool, collection string, volumeId uint32, volumeSize int64) bool {
  172. if *c.readonly && !readOnly {
  173. return true
  174. }
  175. if *c.writable && (readOnly || volumeSize == -1 || c.volumeSizeLimitMb >= uint64(volumeSize)) {
  176. return true
  177. }
  178. if *c.collectionPattern != "" {
  179. if matched, _ := filepath.Match(*c.collectionPattern, collection); !matched {
  180. return true
  181. }
  182. }
  183. if *c.volumeId > 0 && *c.volumeId != uint64(volumeId) {
  184. return true
  185. }
  186. return false
  187. }
  188. func (c *commandVolumeList) writeDiskInfo(writer io.Writer, t *master_pb.DiskInfo, verbosityLevel int, outNodeInfo func()) statistics {
  189. var s statistics
  190. diskType := t.Type
  191. if diskType == "" {
  192. diskType = types.HddType
  193. }
  194. slices.SortFunc(t.VolumeInfos, func(a, b *master_pb.VolumeInformationMessage) int {
  195. return int(a.Id) - int(b.Id)
  196. })
  197. volumeInfosFound := false
  198. for _, vi := range t.VolumeInfos {
  199. if c.isNotMatchDiskInfo(vi.ReadOnly, vi.Collection, vi.Id, int64(vi.Size)) {
  200. continue
  201. }
  202. if !volumeInfosFound {
  203. outNodeInfo()
  204. output(verbosityLevel >= 4, writer, " Disk %s(%s) id:%d\n", diskType, diskInfoToString(t), t.DiskId)
  205. volumeInfosFound = true
  206. }
  207. s = s.plus(writeVolumeInformationMessage(writer, vi, verbosityLevel))
  208. }
  209. ecShardInfoFound := false
  210. for _, ecShardInfo := range t.EcShardInfos {
  211. if c.isNotMatchDiskInfo(false, ecShardInfo.Collection, ecShardInfo.Id, -1) {
  212. continue
  213. }
  214. if !volumeInfosFound && !ecShardInfoFound {
  215. outNodeInfo()
  216. output(verbosityLevel >= 4, writer, " Disk %s(%s) id:%d\n", diskType, diskInfoToString(t), t.DiskId)
  217. ecShardInfoFound = true
  218. }
  219. var expireAtString string
  220. destroyTime := ecShardInfo.ExpireAtSec
  221. if destroyTime > 0 {
  222. expireAtString = fmt.Sprintf("expireAt:%s", time.Unix(int64(destroyTime), 0).Format("2006-01-02 15:04:05"))
  223. }
  224. // Build shard size information
  225. shardIds := erasure_coding.ShardBits(ecShardInfo.EcIndexBits).ShardIds()
  226. var totalSize int64
  227. var shardSizeInfo string
  228. if len(ecShardInfo.ShardSizes) > 0 {
  229. var shardDetails []string
  230. for _, shardId := range shardIds {
  231. if size, found := erasure_coding.GetShardSize(ecShardInfo, erasure_coding.ShardId(shardId)); found {
  232. shardDetails = append(shardDetails, fmt.Sprintf("%d:%s", shardId, util.BytesToHumanReadable(uint64(size))))
  233. totalSize += size
  234. } else {
  235. shardDetails = append(shardDetails, fmt.Sprintf("%d:?", shardId))
  236. }
  237. }
  238. shardSizeInfo = fmt.Sprintf(" sizes:[%s] total:%s", strings.Join(shardDetails, " "), util.BytesToHumanReadable(uint64(totalSize)))
  239. }
  240. output(verbosityLevel >= 5, writer, " ec volume id:%v collection:%v shards:%v%s %s\n",
  241. ecShardInfo.Id, ecShardInfo.Collection, shardIds, shardSizeInfo, expireAtString)
  242. }
  243. output((volumeInfosFound || ecShardInfoFound) && verbosityLevel >= 4, writer, " Disk %s %+v \n", diskType, s)
  244. return s
  245. }
  246. func writeVolumeInformationMessage(writer io.Writer, t *master_pb.VolumeInformationMessage, verbosityLevel int) statistics {
  247. output(verbosityLevel >= 5, writer, " volume %+v \n", t)
  248. return newStatistics(t)
  249. }
  250. func output(condition bool, w io.Writer, format string, a ...interface{}) {
  251. if condition {
  252. fmt.Fprintf(w, format, a...)
  253. }
  254. }
  255. type statistics struct {
  256. Size uint64
  257. FileCount uint64
  258. DeletedFileCount uint64
  259. DeletedBytes uint64
  260. }
  261. func newStatistics(t *master_pb.VolumeInformationMessage) statistics {
  262. return statistics{
  263. Size: t.Size,
  264. FileCount: t.FileCount,
  265. DeletedFileCount: t.DeleteCount,
  266. DeletedBytes: t.DeletedByteCount,
  267. }
  268. }
  269. func (s statistics) plus(t statistics) statistics {
  270. return statistics{
  271. Size: s.Size + t.Size,
  272. FileCount: s.FileCount + t.FileCount,
  273. DeletedFileCount: s.DeletedFileCount + t.DeletedFileCount,
  274. DeletedBytes: s.DeletedBytes + t.DeletedBytes,
  275. }
  276. }
  277. func (s statistics) isEmpty() bool {
  278. return s.Size == 0 && s.FileCount == 0 && s.DeletedFileCount == 0 && s.DeletedBytes == 0
  279. }
  280. func (s statistics) String() string {
  281. if s.DeletedFileCount > 0 {
  282. return fmt.Sprintf("total size:%d file_count:%d deleted_file:%d deleted_bytes:%d", s.Size, s.FileCount, s.DeletedFileCount, s.DeletedBytes)
  283. }
  284. return fmt.Sprintf("total size:%d file_count:%d", s.Size, s.FileCount)
  285. }