added vendor

This commit is contained in:
sm
2015-09-29 01:14:03 -07:00
commit 6662b1e058
9 changed files with 1090 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2014 traetox
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+42
View File
@@ -0,0 +1,42 @@
speedtest
=========
Command line speedtest which interacts with the speedtest.net infrastructure
Example below:
$ /opt/mygo/bin/speedtest -s="Phoe"
1 Matching servers:
------- ---------------- ----------------- ------------------
ID Name Sponsor Distance (km)
------- ---------------- ----------------- ------------------
0 Phoenix, AZ Pavlov Media 1111.75
------- ---------------- ----------------- ------------------
Enter server ID for bandwidth test, or "quit" to exit
ID> 0
Latency: ██▁▁▃▁▁▁▃▁▃▃▅▃▁▃▅▃█▃ 80ms avg 80ms median 83ms max 79ms min
Download: 23.01 Mb/s
Upload: 2.95 Mb/s
$ /opt/mygo/bin/speedtest
Gathering server list and testing...
5 Closest responding servers:
------- -------------------- ------------------------------------- ------------------ -----------------
ID Name Sponsor Distance (km) Latency (ms)
------- -------------------- ------------------------------------- ------------------ -----------------
0 Idaho Falls, ID Microserv 7.65 125.885984ms
1 Idaho Falls, ID Syringa Networks 7.65 138.980347ms
2 Rexburg, ID Brigham Young University - Idaho 48.50 130.055828ms
3 Bozeman, MT Montana Opticom LLC 262.15 114.208524ms
4 Bozeman, MT Global Net 262.15 116.363797ms
------- -------------------- ------------------------------------- ------------------ -----------------
Enter server ID for bandwidth test, or "quit" to exit
ID> 0
Latency: █▇▃▂▂▃▂▃▂▃▂▃▅▁▃▃▂▃▃▂ 123ms avg 123ms median 128ms max 122ms min
Download: 9.73 Mb/s
Upload: 2.89 Mb/s
$
+220
View File
@@ -0,0 +1,220 @@
package main
import (
"errors"
"flag"
"fmt"
"io"
"os"
"strconv"
"strings"
"github.com/Bowery/prompt"
"github.com/bndr/gotabulate"
"github.com/joliv/spark"
stdn "github.com/traetox/speedtest/speedtestdotnet"
//stdn "./speedtestdotnet" //for testing
)
const (
tableFormat = "simple"
maxFailureCount = 3
initialTestCount = 5
basePingCount = 5
fullTestCount = 20
)
var (
speedtestDuration = flag.Int("t", 3, "Target duration for speedtests (in seconds)")
search = flag.String("s", "", "Server name substring to search candidate servers")
)
func init() {
flag.Parse()
if *speedtestDuration <= 0 {
fmt.Fprintf(os.Stderr, "Invalid test duration")
os.Exit(-1)
}
}
func main() {
cfg, err := stdn.GetConfig()
if err != nil {
fmt.Printf("ERROR: %v\n", err)
return
}
if len(cfg.Servers) <= 0 {
fmt.Printf("No acceptable servers found\n")
return
}
var headers []string
var data [][]string
var testServers []stdn.Testserver
if *search == "" {
fmt.Printf("Gathering server list and testing...\n")
if testServers, err = autoGetTestServers(cfg); err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(-1)
}
fmt.Printf("%d Closest responding servers:\n", len(testServers))
for i := range testServers {
data = append(data, []string{fmt.Sprintf("%d", i),
testServers[i].Name, testServers[i].Sponsor,
fmt.Sprintf("%.02f", testServers[i].Distance),
fmt.Sprintf("%s", testServers[i].Latency)})
}
headers = []string{"ID", "Name", "Sponsor", "Distance (km)", "Latency (ms)"}
} else {
if testServers, err = getSearchServers(cfg, *search); err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(-1)
}
headers = []string{"ID", "Name", "Sponsor", "Distance (km)"}
fmt.Printf("%d Matching servers:\n", len(testServers))
for i := range testServers {
data = append(data, []string{fmt.Sprintf("%d", i),
testServers[i].Name, testServers[i].Sponsor,
fmt.Sprintf("%.02f", testServers[i].Distance)})
}
}
t := gotabulate.Create(data)
t.SetHeaders(headers)
t.SetWrapStrings(false)
fmt.Printf("%s", t.Render(tableFormat))
fmt.Printf("Enter server ID for bandwidth test, or \"quit\" to exit\n")
for {
s, err := prompt.Basic("ID> ", true)
if err != nil {
fmt.Printf("input failure \"%v\"\n", err)
os.Exit(-1)
}
//be REALLY forgiving on exit logic
if strings.HasPrefix(strings.ToLower(s), "exit") {
os.Exit(0)
}
if strings.HasPrefix(strings.ToLower(s), "quit") {
os.Exit(0)
}
//try to convert the string to a number
id, err := strconv.ParseUint(s, 10, 64)
if err != nil {
fmt.Fprintf(os.Stderr, "\"%s\" is not a valid id\n", s)
continue
}
if id > uint64(len(testServers)) {
fmt.Fprintf(os.Stderr, "No server with ID \"%d\" available\n", id)
continue
}
if err = fullTest(testServers[id]); err != nil {
if err == io.EOF {
fmt.Fprintf(os.Stderr, "Error, the remote server kicked us.\n")
fmt.Fprintf(os.Stderr, "Maximum request size may have changed\n")
} else {
fmt.Fprintf(os.Stderr, "Test failed with unknown error: %v\n", err)
}
os.Exit(-1)
} else {
break //we are done
}
}
}
func testLatency(server stdn.Testserver) error {
//perform a full latency test
durs, err := server.Ping(fullTestCount)
if err != nil {
return err
}
var avg, max, min uint64
var latencies []float64
for i := range durs {
ms := uint64(durs[i].Nanoseconds() / 1000000)
latencies = append(latencies, float64(ms))
avg += ms
if ms > max {
max = ms
}
if ms < min || min == 0 {
min = ms
}
}
avg = avg / uint64(len(durs))
median := durs[len(durs)/2].Nanoseconds() / 1000000
sparkline := spark.Line(latencies)
fmt.Printf("Latency: %s\t%dms avg\t%dms median\t%dms max\t%dms min\n", sparkline, avg, median, max, min)
return nil
}
func testDownstream(server stdn.Testserver) error {
bps, err := server.Downstream(*speedtestDuration)
if err != nil {
return err
}
fmt.Printf("Download: %s\n", stdn.HumanSpeed(bps))
return nil
}
func testUpstream(server stdn.Testserver) error {
bps, err := server.Upstream(*speedtestDuration)
if err != nil {
return err
}
fmt.Printf("Upload: %s\n", stdn.HumanSpeed(bps))
return nil
}
func fullTest(server stdn.Testserver) error {
if err := testLatency(server); err != nil {
return err
}
if err := testDownstream(server); err != nil {
return err
}
if err := testUpstream(server); err != nil {
return err
}
return nil
}
func autoGetTestServers(cfg *stdn.Config) ([]stdn.Testserver, error) {
//get the first 5 closest servers
testServers := []stdn.Testserver{}
failures := 0
for i := range cfg.Servers {
if failures >= maxFailureCount {
if len(testServers) > 0 {
return testServers, nil
}
return nil, fmt.Errorf("Failed to perform latency test on closest servers\n")
}
if len(testServers) >= initialTestCount {
return testServers, nil
}
//get a latency from the server, the last latency will also be store in the
//server structure
if _, err := cfg.Servers[i].MedianPing(basePingCount); err != nil {
failures++
continue
}
testServers = append(testServers, cfg.Servers[i])
}
return testServers, nil
}
func getSearchServers(cfg *stdn.Config, query string) ([]stdn.Testserver, error) {
//get the first 5 closest servers
testServers := []stdn.Testserver{}
for i := range cfg.Servers {
if strings.Contains(strings.ToLower(cfg.Servers[i].Name), strings.ToLower(query)) {
testServers = append(testServers, cfg.Servers[i])
}
}
if len(testServers) == 0 {
return nil, errors.New("no servers found")
}
return testServers, nil
}
+277
View File
@@ -0,0 +1,277 @@
package speedtestdotnet
import (
"errors"
"fmt"
"io"
"net"
"sort"
"strconv"
"strings"
"time"
)
const (
maxDownstreamTestCount = 4
maxTransferSize = 8 * 1024 * 1024
pingTimeout = time.Second * 5
speedTestTimeout = time.Second * 10
cmdTimeout = time.Second
latencyMaxTestCount = 60
dataBlockSize = 16 * 1024 //16KB
)
var (
errInvalidServerResponse = errors.New("Invalid server response")
errPingFailure = errors.New("Failed to complete ping test")
errDontBeADick = errors.New("requested ping count too high")
startBlockSize = uint64(4096) //4KB
dataBlock []byte
)
func init() {
base := []byte("ABCDEFGHIJ")
dataBlock = make([]byte, dataBlockSize)
for i := range dataBlock {
dataBlock[i] = base[i%len(base)]
}
}
type durations []time.Duration
func (ts *Testserver) ping(count int) ([]time.Duration, error) {
var errRet []time.Duration
if count > latencyMaxTestCount {
return errRet, errDontBeADick
}
//establish connection to the host
conn, err := net.DialTimeout("tcp", ts.Host, pingTimeout)
if err != nil {
return errRet, err
}
defer conn.Close()
durs := []time.Duration{}
buff := make([]byte, 256)
for i := 0; i < count; i++ {
t := time.Now()
fmt.Fprintf(conn, "PING %d\n", uint(t.UnixNano()/1000000))
conn.SetReadDeadline(time.Now().Add(pingTimeout))
n, err := conn.Read(buff)
if err != nil {
return errRet, err
}
conn.SetReadDeadline(time.Time{})
d := time.Since(t)
flds := strings.Fields(strings.TrimRight(string(buff[0:n]), "\n"))
if len(flds) != 2 {
return errRet, errInvalidServerResponse
}
if flds[0] != "PONG" {
return errRet, errInvalidServerResponse
}
if _, err = strconv.ParseInt(flds[1], 10, 64); err != nil {
return errRet, errInvalidServerResponse
}
durs = append(durs, d)
}
if len(durs) != count {
return errRet, errPingFailure
}
return durs, nil
}
//MedianPing runs a latency test against the server and stores the median latency
func (ts *Testserver) MedianPing(count int) (time.Duration, error) {
var errRet time.Duration
durs, err := ts.ping(count)
if err != nil {
return errRet, err
}
sort.Sort(durations(durs))
ts.Latency = durs[count/2]
return durs[count/2], nil
}
//Ping will run count number of latency tests and return the results of each
func (ts *Testserver) Ping(count int) ([]time.Duration, error) {
return ts.ping(count)
}
//throwBytes chucks bytes at the remote server then listens for a response
func throwBytes(conn io.ReadWriter, count uint64) error {
var writeBytes uint64
var b []byte
buff := make([]byte, 128)
for writeBytes < count {
if (count - writeBytes) >= uint64(len(dataBlock)) {
b = dataBlock
} else {
b = dataBlock[0:(count - writeBytes)]
}
n, err := conn.Write(b)
if err != nil {
return err
}
writeBytes += uint64(n)
}
//read the response
n, err := conn.Read(buff)
if err != nil {
return err
}
if n == 0 {
return fmt.Errorf("Failed to get OK on upload")
}
if !strings.HasPrefix(string(buff[0:n]), "OK ") {
return fmt.Errorf("Failed to get OK on upload")
}
return nil
}
//readBytes reads until we get a newline or an error
func readBytes(rdr io.Reader, count uint64) error {
var rBytes uint64
buff := make([]byte, 4096)
for rBytes < count {
n, err := rdr.Read(buff)
if err != nil {
return err
}
rBytes += uint64(n)
if n == 0 {
break
}
if buff[n-1] == '\n' {
break
}
}
if rBytes != count {
return fmt.Errorf("Failed entire read: %d != %d", rBytes, count)
}
return nil
}
//Upstream measures upstream bandwidth in bps
func (ts *Testserver) Upstream(duration int) (uint64, error) {
var currBps uint64
sz := startBlockSize
conn, err := net.DialTimeout("tcp", ts.Host, speedTestTimeout)
if err != nil {
return 0, err
}
targetTestDuration := time.Second * time.Duration(duration)
defer conn.Close()
//we repeat the tests until we have a test that lasts at least N seconds
for i := 0; i < maxDownstreamTestCount; i++ {
//request a download of size sz and set a deadline
if err = conn.SetWriteDeadline(time.Now().Add(cmdTimeout)); err != nil {
return 0, err
}
cmdStr := fmt.Sprintf("UPLOAD %d 0\n", sz)
if _, err := conn.Write([]byte(cmdStr)); err != nil {
return 0, err
}
if err = conn.SetWriteDeadline(time.Time{}); err != nil {
return 0, err
}
ts := time.Now() //set start time mark
if err = conn.SetWriteDeadline(time.Now().Add(speedTestTimeout)); err != nil {
return 0, err
}
if err := throwBytes(conn, sz-uint64(len(cmdStr))); err != nil {
return 0, err
}
if err = conn.SetReadDeadline(time.Time{}); err != nil {
return 0, err
}
//check if our test was a reasonable timespan
dur := time.Since(ts)
currBps = bps(sz, dur)
if dur.Nanoseconds() > targetTestDuration.Nanoseconds() || sz == maxTransferSize {
_, err = fmt.Fprintf(conn, "QUIT\n")
return bps(sz, dur), err
}
//test was too short, try again
sz = calcNextSize(sz, dur)
if sz > maxTransferSize {
sz = maxTransferSize
}
}
_, err = fmt.Fprintf(conn, "QUIT\n")
return currBps, err
}
//Downstream measures upstream bandwidth in bps
func (ts *Testserver) Downstream(duration int) (uint64, error) {
var currBps uint64
sz := startBlockSize
conn, err := net.DialTimeout("tcp", ts.Host, speedTestTimeout)
if err != nil {
return 0, err
}
defer conn.Close()
targetTestDuration := time.Second * time.Duration(duration)
//we repeat the tests until we have a test that lasts at least N seconds
for i := 0; i < maxDownstreamTestCount; i++ {
//request a download of size sz and set a deadline
if err = conn.SetWriteDeadline(time.Now().Add(cmdTimeout)); err != nil {
return 0, err
}
fmt.Fprintf(conn, "DOWNLOAD %d\n", sz)
if err = conn.SetWriteDeadline(time.Time{}); err != nil {
return 0, err
}
ts := time.Now() //set start time mark
if err = conn.SetReadDeadline(time.Now().Add(speedTestTimeout)); err != nil {
return 0, err
}
//read until we get a newline
if err = readBytes(conn, sz); err != nil {
return 0, err
}
if err = conn.SetReadDeadline(time.Time{}); err != nil {
return 0, err
}
//check if our test was a reasonable timespan
dur := time.Since(ts)
currBps = bps(sz, dur)
if dur.Nanoseconds() > targetTestDuration.Nanoseconds() || sz == maxTransferSize {
_, err = fmt.Fprintf(conn, "QUIT\n")
return bps(sz, dur), err
}
//test was too short, try again
sz = calcNextSize(sz, dur)
if sz > maxTransferSize {
sz = maxTransferSize
}
}
_, err = fmt.Fprintf(conn, "QUIT\n")
return currBps, err
}
//calcNextSize takes the current preformance metrics and
//attempts to calculate what the next size should be
func calcNextSize(b uint64, dur time.Duration) uint64 {
if b == 0 {
return startBlockSize
}
target := time.Second * 5
return (b * uint64(target.Nanoseconds())) / uint64(dur.Nanoseconds())
}
//take the byte count and duration and calcuate a bits per second
func bps(byteCount uint64, dur time.Duration) uint64 {
bits := byteCount * 8
return uint64((bits * 1000000000) / uint64(dur.Nanoseconds()))
}
func (d durations) Len() int { return len(d) }
func (d durations) Less(i, j int) bool { return d[i].Nanoseconds() < d[j].Nanoseconds() }
func (d durations) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
+30
View File
@@ -0,0 +1,30 @@
package speedtestdotnet
import (
"fmt"
)
var (
bits = 8
kb = uint64(1024)
mb = 1024 * kb
gb = 1024 * mb
tb = 1024 * gb
pb = 1024 * tb
tooDamnFast = "Too fast to test"
)
func HumanSpeed(bps uint64) string {
if bps > pb {
return tooDamnFast
} else if bps > tb {
return fmt.Sprintf("%.02f Tb/s", float64(bps)/float64(tb))
} else if bps > gb {
return fmt.Sprintf("%.02f Gb/s", float64(bps)/float64(gb))
} else if bps > mb {
return fmt.Sprintf("%.02f Mb/s", float64(bps)/float64(mb))
} else if bps > kb {
return fmt.Sprintf("%.02f Kb/s", float64(bps)/float64(kb))
}
return fmt.Sprintf("%d bps", bps)
}
+204
View File
@@ -0,0 +1,204 @@
package speedtestdotnet
import (
"encoding/xml"
"errors"
"fmt"
"net"
"net/http"
"sort"
"strconv"
"strings"
"time"
"github.com/kellydunn/golang-geo"
)
const (
serversConfigUrl string = `http://www.speedtest.net/speedtest-servers-static.php`
clientConfigUrl string = `http://www.speedtest.net/speedtest-config.php`
getTimeout time.Duration = 2 * time.Second
)
type Testserver struct {
Name string
Sponsor string
Country string
Lat float64
Long float64
Distance float64 //distance from server in KM
URLs []string
Host string
Latency time.Duration //latency in ms
}
type testServerlist []Testserver
type Config struct {
LicenseKey string
IP net.IP
Lat float64
Long float64
ISP string
Servers []Testserver
}
type sconfig struct {
XMLName xml.Name `xml:"server-config"`
Threads int `xml:"threadcount,attr"`
IgnoreIDs string `xml:"ignoreids,attr"`
}
type cconfig struct {
XMLName xml.Name `xml:"client"`
Ip string `xml:"ip,attr"`
Lat float64 `xml:"lat,attr"`
Long float64 `xml:"lon,attr"`
ISP string `xml:"isp,attr"`
ISPUpAvg uint `xml:"ispulavg,attr"`
ISPDlAvg uint `xml:"ispdlavg,attr"`
}
type speedtestConfig struct {
XMLName xml.Name `xml:"settings"`
License string `xml:"licensekey"`
ClientConfig cconfig `xml:"client"`
ServerConfig sconfig `xml:"server-config"`
}
type server struct {
XMLName xml.Name `xml:"server"`
Url string `xml:"url,attr"`
Url2 string `xml:"url2,attr"`
Lat float64 `xml:"lat,attr"`
Long float64 `xml:"lon,attr"`
Name string `xml:"name,attr"`
Country string `xml:"country,attr"`
CC string `xml:"cc,attr"`
Sponsor string `xml:"sponsor,attr"`
ID uint `xml:"id,attr"`
Host string `xml:"host,attr"`
}
type settings struct {
XMLName xml.Name `xml:"settings"`
Servers []server `xml:"servers>server"`
}
//GetServerList returns a list of servers in the native speedtest.net structure
func GetServerList() ([]server, error) {
//get a list of servers
clnt := http.Client{
Timeout: getTimeout,
}
//get the server configs
req, err := http.NewRequest("GET", serversConfigUrl, nil)
if err != nil {
return nil, err
}
resp, err := clnt.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Invalid status %s", resp.StatusCode)
}
xmlDec := xml.NewDecoder(resp.Body)
sts := settings{}
if err := xmlDec.Decode(&sts); err != nil {
return nil, err
}
return sts.Servers, nil
}
//GetConfig returns a configuration containing information about our client and a list of acceptable servers sorted by distance
func GetConfig() (*Config, error) {
//get a client configuration
clnt := http.Client{
Timeout: getTimeout,
}
//get the server configs
req, err := http.NewRequest("GET", clientConfigUrl, nil)
if err != nil {
return nil, err
}
resp, err := clnt.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Invalid status %s", resp.StatusCode)
}
xmlDec := xml.NewDecoder(resp.Body)
cc := speedtestConfig{}
if err := xmlDec.Decode(&cc); err != nil {
return nil, err
}
cfg := Config{
LicenseKey: cc.License,
IP: net.ParseIP(cc.ClientConfig.Ip),
Lat: cc.ClientConfig.Lat,
Long: cc.ClientConfig.Long,
ISP: cc.ClientConfig.ISP,
}
ignoreIDs := make(map[uint]bool, 1)
strIDs := strings.Split(cc.ServerConfig.IgnoreIDs, ",")
for i := range strIDs {
x, err := strconv.ParseUint(strIDs[i], 10, 32)
if err != nil {
continue
}
ignoreIDs[uint(x)] = false
}
srvs, err := GetServerList()
if err != nil {
return nil, err
}
if err := populateServers(&cfg, srvs, ignoreIDs); err != nil {
return nil, err
}
return &cfg, nil
}
func populateServers(cfg *Config, srvs []server, ignore map[uint]bool) error {
for i := range srvs {
//checking if we are ignoring this server
_, ok := ignore[srvs[i].ID]
if ok {
continue
}
srv := Testserver{
Name: srvs[i].Name,
Sponsor: srvs[i].Sponsor,
Country: srvs[i].Country,
Lat: srvs[i].Lat,
Long: srvs[i].Long,
Host: srvs[i].Host,
}
if srvs[i].Url != "" {
srv.URLs = append(srv.URLs, srvs[i].Url)
}
if srvs[i].Url2 != "" {
srv.URLs = append(srv.URLs, srvs[i].Url2)
}
p := geo.NewPoint(cfg.Lat, cfg.Long)
if p == nil {
return errors.New("Invalid client lat/long")
}
sp := geo.NewPoint(srvs[i].Lat, srvs[i].Long)
if sp == nil {
return errors.New("Invalid server lat/long")
}
srv.Distance = p.GreatCircleDistance(sp)
cfg.Servers = append(cfg.Servers, srv)
}
sort.Sort(testServerlist(cfg.Servers))
return nil
}
func (tsl testServerlist) Len() int { return len(tsl) }
func (tsl testServerlist) Swap(i, j int) { tsl[i], tsl[j] = tsl[j], tsl[i] }
func (tsl testServerlist) Less(i, j int) bool { return tsl[i].Distance < tsl[j].Distance }