1
0

ping.go 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. // Defines a wrapper around the system ping binary.
  2. //
  3. // NOTE(os): may fail with non-unix compliant system ping binaries.
  4. package pingo
  5. import (
  6. "errors"
  7. "os/exec"
  8. "strconv"
  9. "strings"
  10. )
  11. // runPing returns the byte array as returned by [exec.Command]
  12. //
  13. // NOTE(os): this function may fail on non-unix compliant implementations of the Ping spec.
  14. func runPing(address string) (delay *[]byte, err error) {
  15. out, err := exec.Command("ping", address, "-c 1").Output()
  16. if err != nil {
  17. return nil, err
  18. }
  19. return &out, err
  20. }
  21. // splitBytesToLines splits bytes as returned by [pingstats.runPing] into an
  22. // array of strings by newline
  23. func splitBytesToLines(bytes *[]byte) (lines []string) {
  24. return strings.Split(strings.ReplaceAll(string(*bytes), "\r\n", "\n"), "\n")
  25. }
  26. // Ping returns the delay of a single Ping as reported by the system Ping binary.
  27. //
  28. // If the function is unable to resolve the system binary output or fails to
  29. // successfully resolve a Ping, it will always return -1.
  30. //
  31. // NOTE(os): this function may fail on non-unix compliant implementations of the Ping spec.
  32. func Ping(address string) (delay float64, err error) {
  33. out, err := runPing(address)
  34. if err != nil {
  35. return -1, err
  36. }
  37. lines := splitBytesToLines(out)
  38. for i := 0; i < len(lines); i++ {
  39. if strings.Contains(lines[i], "bytes from") {
  40. position := strings.Index(lines[i], "time=")
  41. before, _, success := strings.Cut(lines[i][position:], " ")
  42. if !success {
  43. return -1, errors.New("Line does not match pattern!")
  44. }
  45. _, after, success := strings.Cut(before, "=")
  46. if !success {
  47. return -1, errors.New("Line does not match pattern!")
  48. }
  49. delay, _ := strconv.ParseFloat(after, 64)
  50. return delay, nil
  51. }
  52. }
  53. return -1, errors.New("could not resolve host: " + address)
  54. }
  55. type Address struct {
  56. Address string
  57. results []float64
  58. max_results int
  59. }
  60. func (a Address) Truncate() []float64 {
  61. if len(a.results) > a.max_results {
  62. a.results = a.results[len(a.results)-a.max_results : len(a.results)] // return modified slice missing first index
  63. }
  64. return a.results
  65. }
  66. // Wraps [Ping]
  67. func (a Address) Ping() (delay float64, err error) {
  68. return Ping(a.Address)
  69. }
  70. // Poll the affiliated Address and appends it to a.results
  71. func (a Address) Poll() (results []float64, err error) {
  72. delay, err := a.Ping()
  73. a.results = append(a.results, delay)
  74. a.results = a.Truncate() // enforce max length
  75. return a.results, err
  76. }
  77. // Last returns the last result in [Address.results]. Returns -1 if no previous result
  78. func (a Address) Last() (delay float64) {
  79. if len(a.results) > 0 {
  80. return a.results[len(a.results)-1]
  81. } else {
  82. return -1
  83. }
  84. }