mem_solaris.go 5.2 KB

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