Skip to content

Commit

Permalink
ping statistics
Browse files Browse the repository at this point in the history
  • Loading branch information
bbengfort committed Nov 2, 2016
1 parent e80b3a5 commit 0ac38f2
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 110 deletions.
100 changes: 13 additions & 87 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,118 +1,44 @@
# Orca
# Orca Ping

[![Build Status](https://travis-ci.org/bbengfort/orca.svg?branch=master)](https://travis-ci.org/bbengfort/orca)
[![Coverage Status](https://coveralls.io/repos/github/bbengfort/orca/badge.svg?branch=master)](https://coveralls.io/github/bbengfort/orca?branch=master)
[![GoDoc Reference](https://godoc.org/github.com/bbengfort/orca?status.svg)](https://godoc.org/github.com/bbengfort/orca)
[![Go Report Card](https://goreportcard.com/badge/github.com/bbengfort/orca)](https://goreportcard.com/report/github.com/bbengfort/orca)
[![Stories in Ready](https://badge.waffle.io/bbengfort/orca.png?label=ready&title=Ready)](https://waffle.io/bbengfort/orca)

**Echolocation of device with static nodes and network latency.*
**This is the Ping branch of the Orca project**

[![Orca][orca.jpg]][orca_flickr]

Orca is a reflector/ping generator service that is intended to measure network latency of gRPC requests from mobile devices (laptops) to storage locations in home and work networks. Orca was built to get some baseline metrics for latencies in distributed storage systems and is purely experimental, not intended for large scale use.
**Echolocation of device with static nodes and network latency.**

Orca specifies two primary commands: _reflect_ and _generate_. The reflector is a Protocol Buffers service that accepts echo requests and returns replies. The generator is a service that sends a request to all known reflectors on a routine interval and logs the latency in a SQLite database.
Orca is a ping/listener utility that is intended to measure network latency of gRPC requests from mobile devices (laptops) to storage locations in home and work networks. Orca was built to get some baseline metrics for latencies in distributed storage systems and is purely experimental, not intended for large scale use.

## Installation
Orca specifies two primary commands: _listen_ and _ping_. The listener is a Protocol Buffer service that accepts echo requests and returns replies. The ping utility sends echo requests to the specified IP address and measures the aggregate latency.

In order to install Orca and get it running on your system, you have two options: build from source or download a prebuilt binary that may or may not exist for your system.
## Usage

To download the prebuilt binary, visit the [Current Release](#) on GitHub and download the executable for your system. Extract the archive to reveal the executable, and put it on your path, making sure it has executable permissions.

Note: currently only Linux x64 and OS X (darwin) x64 binaries are prebuilt. My preference for path location is `$HOME/bin` but make sure that location is in your path. Otherwise, `/usr/local/bin` is a good choice for moving the binary to.

To build from source, make sure you have your Go environment setup and run:

```
$ go get github.com/bbengfort/orca/...
```

You can then use `go install` to install the binary to `$GOBIN` as follows:
To run a listener on a server use the `orca listen` command:

```
$ go install cmd/orca.go
$ orca listen --help
```

To contribute or modify the orca command, you'll want to go the source route; otherwise if you are using Linux or OS X, I recommend going the binary route. If there is an architecture that you'd like prebuilt, please let me know in a GitHub issue.

## Getting Started

Once installed, some configuration is necessary. This is the recommended configuration, though other configuration options are available and can be inspected via the `orca help` command.

1. Create the directory where configuration and data will reside:

$ mkdir ~/.orca

2. Copy the example configuration file to the orca directory

$ cp fixtures/orca-config-example.yml ~/.orca/config.yml

Note that the first path refers to the fixtures directory inside of the
GitHub repository. If you downloaded the binary package from the releases page, then the example configuration will be in the archive you downloaded.

3. Edit the configuration as needed, noting the defaults and comments in the example configuration file.

4. Create the orca SQLite database used to track meta information about reflection and generators.

$ orca createdb

At this point orca is configured to begin reflecting, however generators require an extra configuration step.

### Relectors

Running orca as a reflector runs a gRPC service that listens for echo requests on the current IP address and port, increments the request sequence (to detect out of order messages), and responds with an echo reply. This process means that there is a small database transaction before reply, so latency will not be exactly the same as using the `ping` utility. If `debug: true` in the configuration, then the server will also log all incoming echo requests to `stdout`, which can be redirected to a log file if required.

Run the reflector as follows:
To ping a listener use the `orca ping` command:

```
$ orca reflect
$ orca ping --help
```

To run in the background on a constant server:

```
$ nohup orca reflect &
```

Upstart and LaunchAgent scripts for managing the background process are forthcoming, though I'd be happy to accept a pull request for them!

### Generators

Generators require a bit more configuration, since you'll have to add all of the reflectors that you want the generator to ping to the database. Do this via the `orca devices --add` command:

```
$ orca devices --add
Enter device name []: rogue
Enter device IP address []: 1.2.3.4:3265
Enter device domain []:
```

Do this for as many devices as you'd like to ping on each interval. To see the devices already added use `orca devices --list`. Run the generator as follows:

```
$ orca generate
```

Similar to the reflector, you'll have to nohup and background this in order to ensure it always runs. LaunchAgent and Upstart scripts are coming soon. The generator waits until the interval has passed, loads up the list of devices to ping, and sends an echo request to them, recording the request (in the case of non-connectivity) and sequence number in the database. On receipt of the reply, it measures latency and stores the information in the database.

## Location Servicesd

Orca can provide location services for mobile devices via the [MaxMind GeoIP2 Precision City Service](https://www.maxmind.com/en/geoip2-precision-city-service). In order to enable location services, you need to register for a MaxMind developer account and include your API user id and license key in the YAML configuration file. Because MaxMind is a paid service, location lookups are only made when the current IP address of the machine changes.

Note also that the granularity for this service is limited; for example, a GeoIP2 lookup from my office in the A.V. Williams Building of the University of Maryland yielded the following location via latitude and longitude:

![Map Granularity](fixtures/map.png)

This location is not centered on my office, the building, or even in the center of the university. The level of granularity probably differs based on the type of network you're connected to. If a higher level of granularity is required, then the use of GPS is recommended. Additional location inaccuracy can come about when tethering to a mobile phone. Use specified locations with care!
If you use it like the ping utility that implements the ICMP protocol, then you're doing it right!

## Acknowledgements

Orca is an open source project built to obtain metrics about mobile distributed systems and various latencies. If you'd like to contribute, I'd love some help, but no current plans are underway for future development.

### Attribution

The photo used in this README, “[Orca (Design Exploration)][orca_flickr]” by [Alberto Cerriteño](https://www.flickr.com/photos/acerriteno/) is used under a [CC BY-NC-ND 2.0](https://creativecommons.org/licenses/by-nc-nd/2.0/) creative commons license.
The photo used in this README, “[Orca Sighting][orca_flickr]” by [Jay Cox](https://www.flickr.com/photos/jaycoxfilm/) is used under a [CC BY-NC-ND 2.0](https://creativecommons.org/licenses/by-nc-nd/2.0/) creative commons license.

[orca.jpg]: fixtures/orca.jpg
[orca_flickr]: https://flic.kr/p/4HDnoE
[orca_flickr]: https://flic.kr/p/4Nkop2
53 changes: 40 additions & 13 deletions cmd/orca.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,38 @@ func main() {
app.Email = "bengfort@cs.umd.edu"
app.Commands = []cli.Command{
{
Name: "listen",
Usage: "listen for pings",
Action: listen,
Name: "listen",
Usage: "listen for pings",
Action: listen,
ArgsUsage: " ",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "s, silent",
Usage: "do not log messages",
},
cli.StringFlag{
Name: "a, addr",
Usage: "specify the address to listen on",
Value: ":3265",
},
},
},
{
Name: "ping",
Usage: "send echo requests to the specified address",
Action: ping,
Name: "ping",
Usage: "send echo requests to the specified address",
Action: ping,
ArgsUsage: "ipaddr",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "s, silent",
Usage: "do not log messages",
},
cli.IntFlag{
Name: "n, num",
Usage: "limit the number of echo requests",
Value: 4,
},
},
},
}

Expand All @@ -47,7 +65,11 @@ func main() {

func listen(c *cli.Context) error {

orcaApp, err := orca.Init(false)
// Get the arguments from the command line
silent := c.Bool("silent")
addr := c.String("addr")

orcaApp, err := orca.Init(silent, addr)
if err != nil {
return cli.NewExitError(err.Error(), 1)
}
Expand All @@ -61,18 +83,23 @@ func listen(c *cli.Context) error {

func ping(c *cli.Context) error {

orcaApp, err := orca.Init(false)
if err != nil {
return cli.NewExitError(err.Error(), 1)
}

// Validate arguments
if c.NArg() != 1 {
return cli.NewExitError("Specify an IP address to ping", 1)
}

// Get the arguments from the command line
silent := c.Bool("silent")
count := c.Int("num")
addr := c.Args()[0]

if err := orcaApp.Generate(addr); err != nil {
// Initialize the app
orcaApp, err := orca.Init(silent, "")
if err != nil {
return cli.NewExitError(err.Error(), 1)
}

if err := orcaApp.Generate(addr, count); err != nil {
return cli.NewExitError(err.Error(), 1)
}

Expand Down
Binary file added fixtures/orca.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 6 additions & 2 deletions listen.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"google.golang.org/grpc"
)

var responses int64

// Echo implements the echo.EchoServer interface on the App
func (app *App) Echo(ctx context.Context, in *Request) (*Reply, error) {

Expand All @@ -20,10 +22,12 @@ func (app *App) Echo(ctx context.Context, in *Request) (*Reply, error) {
log.Println(in.LogRecord())
}

responses++

// Return the Reply
return &Reply{
Sequence: 0,
Receiver: nil,
Sequence: responses,
Receiver: &Device{Name: "orca", IPAddr: app.IPAddr},
Received: &Time{Nanoseconds: recv.UnixNano()},
Echo: in,
}, nil
Expand Down
45 changes: 43 additions & 2 deletions orca.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@
// latency and uptime of mobile nodes against fixed responder nodes.
package orca

import (
"fmt"
"math"
)

// Version specifies the current version of the Orca library.
const Version = "0.1"
const Version = "ping"

// App is the primary application that maintains references to the config
// and device details as well as initializes the environment and runs the
Expand All @@ -16,9 +21,10 @@ type App struct {

// Init the orca application
// NOTE: SyncLocation should not be called in init!
func Init(silent bool) (*App, error) {
func Init(silent bool, addr string) (*App, error) {
app := new(App)
app.Silent = silent
app.IPAddr = addr

ipaddr, err := app.GetListenAddr()
if err != nil {
Expand All @@ -34,3 +40,38 @@ func Init(silent bool) (*App, error) {
func (app *App) GetListenAddr() (string, error) {
return ResolveAddr(app.IPAddr)
}

// ComputeStats aggregates the latencies and prints out a description of the
// statistical measurements of the pings recorded.
func (app *App) ComputeStats() {
// Compute the statistics of the latencies
num := 0.0
sum := 0.0
ssq := 0.0
min := -1.0
max := -1.0

for _, latency := range app.Latencies {
num += 1.0
sum += latency

if min < 0.0 || latency < min {
min = latency
}

if max < 0.0 || latency > max {
max = latency
}
}

mean := sum / num

for _, latency := range app.Latencies {
ssq += math.Pow(latency-mean, 2)
}

sd := math.Sqrt(ssq / num)

fmt.Println("--- echo statistics ---")
fmt.Printf("round-trip min/avg/max/stddev = %0.3f/%0.3f/%0.3f/%0.3f ms\n", min, mean, max, sd)
}
2 changes: 1 addition & 1 deletion orca_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
var _ = Describe("Orca", func() {

It("should be at version 0.1", func() {
Ω(Version).Should(Equal("0.1"))
Ω(Version).Should(Equal("ping"))
})

})
17 changes: 12 additions & 5 deletions ping.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ const Interval = time.Duration(1) * time.Second
// Timeout is the amount of time sonar will wait for a reply
const Timeout = time.Duration(30) * time.Second

var sequence int64

// Generate is long running function that initializes pings then sleeps.
func (app *App) Generate(addr string) error {
func (app *App) Generate(addr string, count int) error {

// Loop forever with a delay between the interval
for {
for i := 0; i < count; i++ {

// Wait for the specified interval
select {
Expand All @@ -34,6 +36,9 @@ func (app *App) Generate(addr string) error {

}

// Compute the statistics
app.ComputeStats()
return nil
}

// Ping sends an echo request to a device and handles the response
Expand Down Expand Up @@ -76,13 +81,15 @@ func (app *App) SendPing(addr string) (*Reply, error) {
defer conn.Close()
client := NewOrcaClient(conn)

sequence++

// Create an EchoRequest to send to the node
request := &Request{
Sequence: 0,
Sender: nil,
Sequence: sequence,
Sender: &Device{Name: "orca", IPAddr: app.IPAddr},
Sent: &Time{Nanoseconds: time.Now().UnixNano()},
TTL: int64(Timeout.Seconds()),
Ping: 0,
Ping: sequence,
Payload: []byte("Clutter to be replaced with random or actual data."),
}

Expand Down

0 comments on commit 0ac38f2

Please sign in to comment.