// Defines a wrapper around the system ping binary. // // NOTE(os): may fail with non-unix compliant system ping binaries. package pingo import ( "errors" "os/exec" "strconv" "strings" ) // runPing returns the byte array as returned by [exec.Command] // // NOTE(os): this function may fail on non-unix compliant implementations of the Ping spec. func runPing(address string) (delay *[]byte, err error) { out, err := exec.Command("ping", address, "-c 1").Output() if err != nil { return nil, err } return &out, err } // splitBytesToLines splits bytes as returned by [pingstats.runPing] into an // array of strings by newline func splitBytesToLines(bytes *[]byte) (lines []string) { return strings.Split(strings.ReplaceAll(string(*bytes), "\r\n", "\n"), "\n") } // Ping returns the delay of a single Ping as reported by the system Ping binary. // // If the function is unable to resolve the system binary output or fails to // successfully resolve a Ping, it will always return -1. // // NOTE(os): this function may fail on non-unix compliant implementations of the Ping spec. func Ping(address string) (delay float64, err error) { out, err := runPing(address) if err != nil { return -1, err } lines := splitBytesToLines(out) for i := 0; i < len(lines); i++ { if strings.Contains(lines[i], "bytes from") { position := strings.Index(lines[i], "time=") before, _, success := strings.Cut(lines[i][position:], " ") if !success { return -1, errors.New("Line does not match pattern!") } _, after, success := strings.Cut(before, "=") if !success { return -1, errors.New("Line does not match pattern!") } delay, _ := strconv.ParseFloat(after, 64) return delay, nil } } return -1, errors.New("could not resolve host: " + address) } type Address struct { Address string results []float64 max_results int } func (a Address) Truncate() []float64 { if len(a.results) > a.max_results { a.results = a.results[len(a.results)-a.max_results : len(a.results)] // return modified slice missing first index } return a.results } // Wraps [Ping] func (a Address) Ping() (delay float64, err error) { return Ping(a.Address) } // Poll the affiliated Address and appends it to a.results func (a Address) Poll() (results []float64, err error) { delay, err := a.Ping() a.results = append(a.results, delay) a.results = a.Truncate() // enforce max length return a.results, err } // Last returns the last result in [Address.results]. Returns -1 if no previous result func (a Address) Last() (delay float64) { if len(a.results) > 0 { return a.results[len(a.results)-1] } else { return -1 } }