mem_bsd.go 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. // +build freebsd openbsd
  2. package mem
  3. import (
  4. "context"
  5. "fmt"
  6. "os/exec"
  7. "strconv"
  8. "strings"
  9. )
  10. const swapCommand = "swapctl"
  11. // swapctl column indexes
  12. const (
  13. nameCol = 0
  14. totalKiBCol = 1
  15. usedKiBCol = 2
  16. )
  17. func SwapDevices() ([]*SwapDevice, error) {
  18. return SwapDevicesWithContext(context.Background())
  19. }
  20. func SwapDevicesWithContext(ctx context.Context) ([]*SwapDevice, error) {
  21. swapCommandPath, err := exec.LookPath(swapCommand)
  22. if err != nil {
  23. return nil, fmt.Errorf("could not find command %q: %w", swapCommand, err)
  24. }
  25. output, err := invoke.CommandWithContext(ctx, swapCommandPath, "-lk")
  26. if err != nil {
  27. return nil, fmt.Errorf("could not execute %q: %w", swapCommand, err)
  28. }
  29. return parseSwapctlOutput(string(output))
  30. }
  31. func parseSwapctlOutput(output string) ([]*SwapDevice, error) {
  32. lines := strings.Split(output, "\n")
  33. if len(lines) == 0 {
  34. return nil, fmt.Errorf("could not parse output of %q: no lines in %q", swapCommand, output)
  35. }
  36. // Check header headerFields are as expected.
  37. header := lines[0]
  38. header = strings.ToLower(header)
  39. header = strings.ReplaceAll(header, ":", "")
  40. headerFields := strings.Fields(header)
  41. if len(headerFields) < usedKiBCol {
  42. return nil, fmt.Errorf("couldn't parse %q: too few fields in header %q", swapCommand, header)
  43. }
  44. if headerFields[nameCol] != "device" {
  45. return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapCommand, headerFields[nameCol], "device")
  46. }
  47. if headerFields[totalKiBCol] != "1kb-blocks" && headerFields[totalKiBCol] != "1k-blocks" {
  48. return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapCommand, headerFields[totalKiBCol], "1kb-blocks")
  49. }
  50. if headerFields[usedKiBCol] != "used" {
  51. return nil, fmt.Errorf("couldn't parse %q: expected %q to be %q", swapCommand, headerFields[usedKiBCol], "used")
  52. }
  53. var swapDevices []*SwapDevice
  54. for _, line := range lines[1:] {
  55. if line == "" {
  56. continue // the terminal line is typically empty
  57. }
  58. fields := strings.Fields(line)
  59. if len(fields) < usedKiBCol {
  60. return nil, fmt.Errorf("couldn't parse %q: too few fields", swapCommand)
  61. }
  62. totalKiB, err := strconv.ParseUint(fields[totalKiBCol], 10, 64)
  63. if err != nil {
  64. return nil, fmt.Errorf("couldn't parse 'Size' column in %q: %w", swapCommand, err)
  65. }
  66. usedKiB, err := strconv.ParseUint(fields[usedKiBCol], 10, 64)
  67. if err != nil {
  68. return nil, fmt.Errorf("couldn't parse 'Used' column in %q: %w", swapCommand, err)
  69. }
  70. swapDevices = append(swapDevices, &SwapDevice{
  71. Name: fields[nameCol],
  72. UsedBytes: usedKiB * 1024,
  73. FreeBytes: (totalKiB - usedKiB) * 1024,
  74. })
  75. }
  76. return swapDevices, nil
  77. }