mem_solaris.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. //go:build solaris
  2. // +build solaris
  3. package mem
  4. import (
  5. "context"
  6. "errors"
  7. "fmt"
  8. "regexp"
  9. "strconv"
  10. "strings"
  11. "github.com/shirou/gopsutil/v3/internal/common"
  12. )
  13. // VirtualMemory for Solaris is a minimal implementation which only returns
  14. // what Nomad needs. It does take into account global vs zone, however.
  15. func VirtualMemory() (*VirtualMemoryStat, error) {
  16. return VirtualMemoryWithContext(context.Background())
  17. }
  18. func VirtualMemoryWithContext(ctx context.Context) (*VirtualMemoryStat, error) {
  19. result := &VirtualMemoryStat{}
  20. zoneName, err := zoneName()
  21. if err != nil {
  22. return nil, err
  23. }
  24. if zoneName == "global" {
  25. cap, err := globalZoneMemoryCapacity()
  26. if err != nil {
  27. return nil, err
  28. }
  29. result.Total = cap
  30. } else {
  31. cap, err := nonGlobalZoneMemoryCapacity()
  32. if err != nil {
  33. return nil, err
  34. }
  35. result.Total = cap
  36. }
  37. return result, nil
  38. }
  39. func SwapMemory() (*SwapMemoryStat, error) {
  40. return SwapMemoryWithContext(context.Background())
  41. }
  42. func SwapMemoryWithContext(ctx context.Context) (*SwapMemoryStat, error) {
  43. return nil, common.ErrNotImplementedError
  44. }
  45. func zoneName() (string, error) {
  46. ctx := context.Background()
  47. out, err := invoke.CommandWithContext(ctx, "zonename")
  48. if err != nil {
  49. return "", err
  50. }
  51. return strings.TrimSpace(string(out)), nil
  52. }
  53. var globalZoneMemoryCapacityMatch = regexp.MustCompile(`[Mm]emory size: (\d+) Megabytes`)
  54. func globalZoneMemoryCapacity() (uint64, error) {
  55. ctx := context.Background()
  56. out, err := invoke.CommandWithContext(ctx, "prtconf")
  57. if err != nil {
  58. return 0, err
  59. }
  60. match := globalZoneMemoryCapacityMatch.FindAllStringSubmatch(string(out), -1)
  61. if len(match) != 1 {
  62. return 0, errors.New("memory size not contained in output of prtconf")
  63. }
  64. totalMB, err := strconv.ParseUint(match[0][1], 10, 64)
  65. if err != nil {
  66. return 0, err
  67. }
  68. return totalMB * 1024 * 1024, nil
  69. }
  70. var kstatMatch = regexp.MustCompile(`(\S+)\s+(\S*)`)
  71. func nonGlobalZoneMemoryCapacity() (uint64, error) {
  72. ctx := context.Background()
  73. out, err := invoke.CommandWithContext(ctx, "kstat", "-p", "-c", "zone_memory_cap", "memory_cap:*:*:physcap")
  74. if err != nil {
  75. return 0, err
  76. }
  77. kstats := kstatMatch.FindAllStringSubmatch(string(out), -1)
  78. if len(kstats) != 1 {
  79. return 0, fmt.Errorf("expected 1 kstat, found %d", len(kstats))
  80. }
  81. memSizeBytes, err := strconv.ParseUint(kstats[0][2], 10, 64)
  82. if err != nil {
  83. return 0, err
  84. }
  85. return memSizeBytes, nil
  86. }
  87. const swapCommand = "swap"
  88. // The blockSize as reported by `swap -l`. See https://docs.oracle.com/cd/E23824_01/html/821-1459/fsswap-52195.html
  89. const blockSize = 512
  90. // swapctl column indexes
  91. const (
  92. nameCol = 0
  93. // devCol = 1
  94. // swaploCol = 2
  95. totalBlocksCol = 3
  96. freeBlocksCol = 4
  97. )
  98. func SwapDevices() ([]*SwapDevice, error) {
  99. return SwapDevicesWithContext(context.Background())
  100. }
  101. func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) {
  102. output, err := invoke.CommandWithContext(ctx, swapCommand, "-l")
  103. if err != nil {
  104. return nil, fmt.Errorf("could not execute %q: %w", swapCommand, err)
  105. }
  106. return parseSwapsCommandOutput(string(output))
  107. }
  108. func parseSwapsCommandOutput(output string) ([]*SwapDevice, error) {
  109. lines := strings.Split(output, "\n")
  110. if len(lines) == 0 {
  111. return nil, fmt.Errorf("could not parse output of %q: no lines in %q", swapCommand, output)
  112. }
  113. // Check header headerFields are as expected.
  114. headerFields := strings.Fields(lines[0])
  115. if len(headerFields) < freeBlocksCol {
  116. return nil, fmt.Errorf("couldn't parse %q: too few fields in header %q", swapCommand, lines[0])
  117. }
  118. if headerFields[nameCol] != "swapfile" {
  119. return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapCommand, headerFields[nameCol], "swapfile")
  120. }
  121. if headerFields[totalBlocksCol] != "blocks" {
  122. return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapCommand, headerFields[totalBlocksCol], "blocks")
  123. }
  124. if headerFields[freeBlocksCol] != "free" {
  125. return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapCommand, headerFields[freeBlocksCol], "free")
  126. }
  127. var swapDevices []*SwapDevice
  128. for _, line := range lines[1:] {
  129. if line == "" {
  130. continue // the terminal line is typically empty
  131. }
  132. fields := strings.Fields(line)
  133. if len(fields) < freeBlocksCol {
  134. return nil, fmt.Errorf("couldn't parse %q: too few fields", swapCommand)
  135. }
  136. totalBlocks, err := strconv.ParseUint(fields[totalBlocksCol], 10, 64)
  137. if err != nil {
  138. return nil, fmt.Errorf("couldn't parse 'Size' column in %q: %w", swapCommand, err)
  139. }
  140. freeBlocks, err := strconv.ParseUint(fields[freeBlocksCol], 10, 64)
  141. if err != nil {
  142. return nil, fmt.Errorf("couldn't parse 'Used' column in %q: %w", swapCommand, err)
  143. }
  144. swapDevices = append(swapDevices, &SwapDevice{
  145. Name: fields[nameCol],
  146. UsedBytes: (totalBlocks - freeBlocks) * blockSize,
  147. FreeBytes: freeBlocks * blockSize,
  148. })
  149. }
  150. return swapDevices, nil
  151. }