mem_bsd.go 2.5 KB

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