| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- // TODO(performance): implement ping message channel buffering
- //
- // Pingo defines a number of high level functions and data structures to
- // interact with and represent a Ping via Go. Primarily, it defines the
- // Ping() function and Address data structure.
- //
- // NOTE this project does not provide a Go implementation of the ping protocol.
- // Instead, this project merely provides a wrapper to your system's existing
- // ping.c binary.
- //
- // NOTE(os): this project may fail with non-unix compliant system ping binaries,
- // predominantly Windows platforms. This relates to the way in which the project
- // interacts with the ping binary. (read: the project uses `ping -c1 ADDRESS`
- // and will break on systems that implement a different CLI)
- //
- // The project defines a simple top level binding for the system's ping.c:
- // Ping(). It simply reports a delay time as reported by the system ping, and
- // any errors encountered.
- //
- // Additionally, the project defines the "Address" data structure to represent
- // a series of ping results relating to the given host. Its fields are simple:
- //
- // Address (string): the host or IP address
- //
- // Results ([]float64): a series of ping results
- //
- // MaxResults (int): The maximum number of results to track, as enforced by
- // Address.Truncate()
- //
- // The Address data structure provides a series of higher level access functions
- // to prevent code repetition:
- //
- // Example:
- // ...
- // a := Address{Address:"google.ca", MaxResults:20}
- // latency, err := a.Ping() // get a single result and return the value
- // ... // handle error
- // a.Results, err = a.Poll() // append a ping to the Results slice, then return the updated slice
- // ... // handle error
- // a.Results = a.Truncate() // return a modified slice of only the most n*a.MaxResults long
- //
- // For more, see each function's GoDoc.
- 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 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 := range lines {
- 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
- MaxResults int
- }
- // Enforces a Address.Result maximum length by dropping the oldest result,
- // then returns the modified slice.
- func (a Address) Truncate() []float64 {
- if len(a.Results) > a.MaxResults {
- a.Results = a.Results[len(a.Results)-a.MaxResults : len(a.Results)] // return modified slice missing first index
- }
- return a.Results
- }
- // Wraps Ping, passing Address.Address as its argument.
- func (a Address) Ping() (delay float64, err error) {
- return Ping(a.Address)
- }
- // Poll the affiliated Address, append it to a.Results, and report the modified
- // slice and any errors.
- 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
- }
- }
|