added vendor
This commit is contained in:
commit
6662b1e058
7
vendor/github.com/kellydunn/golang-geo/MIT_LICENSE
generated
vendored
Normal file
7
vendor/github.com/kellydunn/golang-geo/MIT_LICENSE
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
Copyright (c) 2013 Kelly Dunn
|
||||||
|
|
||||||
|
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.
|
150
vendor/github.com/kellydunn/golang-geo/point.go
generated
vendored
Normal file
150
vendor/github.com/kellydunn/golang-geo/point.go
generated
vendored
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
package geo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Represents a Physical Point in geographic notation [lat, lng].
|
||||||
|
type Point struct {
|
||||||
|
lat float64
|
||||||
|
lng float64
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// According to Wikipedia, the Earth's radius is about 6,371km
|
||||||
|
EARTH_RADIUS = 6371
|
||||||
|
)
|
||||||
|
|
||||||
|
// Returns a new Point populated by the passed in latitude (lat) and longitude (lng) values.
|
||||||
|
func NewPoint(lat float64, lng float64) *Point {
|
||||||
|
return &Point{lat: lat, lng: lng}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns Point p's latitude.
|
||||||
|
func (p *Point) Lat() float64 {
|
||||||
|
return p.lat
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns Point p's longitude.
|
||||||
|
func (p *Point) Lng() float64 {
|
||||||
|
return p.lng
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a Point populated with the lat and lng coordinates
|
||||||
|
// by transposing the origin point the passed in distance (in kilometers)
|
||||||
|
// by the passed in compass bearing (in degrees).
|
||||||
|
// Original Implementation from: http://www.movable-type.co.uk/scripts/latlong.html
|
||||||
|
func (p *Point) PointAtDistanceAndBearing(dist float64, bearing float64) *Point {
|
||||||
|
|
||||||
|
dr := dist / EARTH_RADIUS
|
||||||
|
|
||||||
|
bearing = (bearing * (math.Pi / 180.0))
|
||||||
|
|
||||||
|
lat1 := (p.lat * (math.Pi / 180.0))
|
||||||
|
lng1 := (p.lng * (math.Pi / 180.0))
|
||||||
|
|
||||||
|
lat2_part1 := math.Sin(lat1) * math.Cos(dr)
|
||||||
|
lat2_part2 := math.Cos(lat1) * math.Sin(dr) * math.Cos(bearing)
|
||||||
|
|
||||||
|
lat2 := math.Asin(lat2_part1 + lat2_part2)
|
||||||
|
|
||||||
|
lng2_part1 := math.Sin(bearing) * math.Sin(dr) * math.Cos(lat1)
|
||||||
|
lng2_part2 := math.Cos(dr) - (math.Sin(lat1) * math.Sin(lat2))
|
||||||
|
|
||||||
|
lng2 := lng1 + math.Atan2(lng2_part1, lng2_part2)
|
||||||
|
lng2 = math.Mod((lng2+3*math.Pi), (2*math.Pi)) - math.Pi
|
||||||
|
|
||||||
|
lat2 = lat2 * (180.0 / math.Pi)
|
||||||
|
lng2 = lng2 * (180.0 / math.Pi)
|
||||||
|
|
||||||
|
return &Point{lat: lat2, lng: lng2}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculates the Haversine distance between two points in kilometers.
|
||||||
|
// Original Implementation from: http://www.movable-type.co.uk/scripts/latlong.html
|
||||||
|
func (p *Point) GreatCircleDistance(p2 *Point) float64 {
|
||||||
|
dLat := (p2.lat - p.lat) * (math.Pi / 180.0)
|
||||||
|
dLon := (p2.lng - p.lng) * (math.Pi / 180.0)
|
||||||
|
|
||||||
|
lat1 := p.lat * (math.Pi / 180.0)
|
||||||
|
lat2 := p2.lat * (math.Pi / 180.0)
|
||||||
|
|
||||||
|
a1 := math.Sin(dLat/2) * math.Sin(dLat/2)
|
||||||
|
a2 := math.Sin(dLon/2) * math.Sin(dLon/2) * math.Cos(lat1) * math.Cos(lat2)
|
||||||
|
|
||||||
|
a := a1 + a2
|
||||||
|
|
||||||
|
c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
|
||||||
|
|
||||||
|
return EARTH_RADIUS * c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculates the initial bearing (sometimes referred to as forward azimuth)
|
||||||
|
// Original Implementation from: http://www.movable-type.co.uk/scripts/latlong.html
|
||||||
|
func (p *Point) BearingTo(p2 *Point) float64 {
|
||||||
|
|
||||||
|
dLon := (p2.lng - p.lng) * math.Pi / 180.0
|
||||||
|
|
||||||
|
lat1 := p.lat * math.Pi / 180.0
|
||||||
|
lat2 := p2.lat * math.Pi / 180.0
|
||||||
|
|
||||||
|
y := math.Sin(dLon) * math.Cos(lat2)
|
||||||
|
x := math.Cos(lat1)*math.Sin(lat2) -
|
||||||
|
math.Sin(lat1)*math.Cos(lat2)*math.Cos(dLon)
|
||||||
|
brng := math.Atan2(y, x) * 180.0 / math.Pi
|
||||||
|
|
||||||
|
return brng
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculates the midpoint between 'this' point and the supplied point.
|
||||||
|
// Original implementation from http://www.movable-type.co.uk/scripts/latlong.html
|
||||||
|
func (p *Point) MidpointTo(p2 *Point) *Point {
|
||||||
|
lat1 := p.lat * math.Pi / 180.0
|
||||||
|
lat2 := p2.lat * math.Pi / 180.0
|
||||||
|
|
||||||
|
lon1 := p.lng * math.Pi / 180.0
|
||||||
|
dLon := (p2.lng - p.lng) * math.Pi / 180.0
|
||||||
|
|
||||||
|
bx := math.Cos(lat2) * math.Cos(dLon)
|
||||||
|
by := math.Cos(lat2) * math.Sin(dLon)
|
||||||
|
|
||||||
|
lat3Rad := math.Atan2(
|
||||||
|
math.Sin(lat1)+math.Sin(lat2),
|
||||||
|
math.Sqrt(math.Pow(math.Cos(lat1)+bx, 2)+math.Pow(by, 2)),
|
||||||
|
)
|
||||||
|
lon3Rad := lon1 + math.Atan2(by, math.Cos(lat1)+bx)
|
||||||
|
|
||||||
|
lat3 := lat3Rad * 180.0 / math.Pi
|
||||||
|
lon3 := lon3Rad * 180.0 / math.Pi
|
||||||
|
|
||||||
|
return NewPoint(lat3, lon3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renders the current Point to valid JSON.
|
||||||
|
// Implements the json.Marshaller Interface.
|
||||||
|
func (p *Point) MarshalJSON() ([]byte, error) {
|
||||||
|
res := fmt.Sprintf(`{"lat":%v, "lng":%v}`, p.lat, p.lng)
|
||||||
|
return []byte(res), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decodes the current Point from a JSON body.
|
||||||
|
// Throws an error if the body of the point cannot be interpreted by the JSON body
|
||||||
|
func (p *Point) UnmarshalJSON(data []byte) error {
|
||||||
|
// TODO throw an error if there is an issue parsing the body.
|
||||||
|
dec := json.NewDecoder(bytes.NewReader(data))
|
||||||
|
var values map[string]float64
|
||||||
|
err := dec.Decode(&values)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*p = *NewPoint(values["lat"], values["lng"])
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
138
vendor/github.com/kellydunn/golang-geo/point_test.go
generated
vendored
Normal file
138
vendor/github.com/kellydunn/golang-geo/point_test.go
generated
vendored
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
package geo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tests that a call to NewPoint should return a pointer to a Point with the specified values assigned correctly.
|
||||||
|
func TestNewPoint(t *testing.T) {
|
||||||
|
p := NewPoint(40.5, 120.5)
|
||||||
|
|
||||||
|
if p == nil {
|
||||||
|
t.Error("Expected to get a pointer to a new point, but got nil instead.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.lat != 40.5 {
|
||||||
|
t.Errorf("Expected to be able to specify 40.5 as the lat value of a new point, but got %f instead", p.lat)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.lng != 120.5 {
|
||||||
|
t.Errorf("Expected to be able to specify 120.5 as the lng value of a new point, but got %f instead", p.lng)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that calling GetLat() after creating a new point returns the expected lat value.
|
||||||
|
func TestLat(t *testing.T) {
|
||||||
|
p := NewPoint(40.5, 120.5)
|
||||||
|
|
||||||
|
lat := p.Lat()
|
||||||
|
|
||||||
|
if lat != 40.5 {
|
||||||
|
t.Error("Expected a call to GetLat() to return the same lat value as was set before, but got %f instead", lat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that calling GetLng() after creating a new point returns the expected lng value.
|
||||||
|
func TestLng(t *testing.T) {
|
||||||
|
p := NewPoint(40.5, 120.5)
|
||||||
|
|
||||||
|
lng := p.Lng()
|
||||||
|
|
||||||
|
if lng != 120.5 {
|
||||||
|
t.Error("Expected a call to GetLng() to return the same lat value as was set before, but got %f instead", lng)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seems brittle :\
|
||||||
|
func TestGreatCircleDistance(t *testing.T) {
|
||||||
|
// Test that SEA and SFO are ~ 1091km apart, accurate to 100 meters.
|
||||||
|
sea := &Point{lat: 47.4489, lng: -122.3094}
|
||||||
|
sfo := &Point{lat: 37.6160933, lng: -122.3924223}
|
||||||
|
sfoToSea := 1093.379199082169
|
||||||
|
|
||||||
|
dist := sea.GreatCircleDistance(sfo)
|
||||||
|
|
||||||
|
if !(dist < (sfoToSea+0.1) && dist > (sfoToSea-0.1)) {
|
||||||
|
t.Error("Unnacceptable result.", dist)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPointAtDistanceAndBearing(t *testing.T) {
|
||||||
|
sea := &Point{lat: 47.44745785, lng: -122.308065668024}
|
||||||
|
p := sea.PointAtDistanceAndBearing(1090.7, 180)
|
||||||
|
|
||||||
|
// Expected results of transposing point
|
||||||
|
// ~1091km at bearing of 180 degrees
|
||||||
|
resultLat := 37.638557
|
||||||
|
resultLng := -122.308066
|
||||||
|
|
||||||
|
withinLatBounds := p.lat < resultLat+0.001 && p.lat > resultLat-0.001
|
||||||
|
withinLngBounds := p.lng < resultLng+0.001 && p.lng > resultLng-0.001
|
||||||
|
if !(withinLatBounds && withinLngBounds) {
|
||||||
|
t.Error("Unnacceptable result.", fmt.Sprintf("[%f, %f]", p.lat, p.lng))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBearingTo(t *testing.T) {
|
||||||
|
p1 := &Point{lat: 40.7486, lng: -73.9864}
|
||||||
|
p2 := &Point{lat: 0.0, lng: 0.0}
|
||||||
|
bearing := p1.BearingTo(p2)
|
||||||
|
|
||||||
|
// Expected bearing 60 degrees
|
||||||
|
resultBearing := 100.610833
|
||||||
|
|
||||||
|
withinBearingBounds := bearing < resultBearing+0.001 && bearing > resultBearing-0.001
|
||||||
|
if !withinBearingBounds {
|
||||||
|
t.Error("Unnacceptable result.", fmt.Sprintf("%f", bearing))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMidpointTo(t *testing.T) {
|
||||||
|
p1 := &Point{lat: 52.205, lng: 0.119}
|
||||||
|
p2 := &Point{lat: 48.857, lng: 2.351}
|
||||||
|
|
||||||
|
p := p1.MidpointTo(p2)
|
||||||
|
|
||||||
|
// Expected midpoint 50.5363°N, 001.2746°E
|
||||||
|
resultLat := 50.53632
|
||||||
|
resultLng := 1.274614
|
||||||
|
|
||||||
|
withinLatBounds := p.lat < resultLat+0.001 && p.lat > resultLat-0.001
|
||||||
|
withinLngBounds := p.lng < resultLng+0.001 && p.lng > resultLng-0.001
|
||||||
|
if !(withinLatBounds && withinLngBounds) {
|
||||||
|
t.Error("Unnacceptable result.", fmt.Sprintf("[%f, %f]", p.lat, p.lng))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enures that a point can be marhalled into JSON
|
||||||
|
func TestMarshalJSON(t *testing.T) {
|
||||||
|
p := NewPoint(40.7486, -73.9864)
|
||||||
|
res, err := json.Marshal(p)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
t.Error("Should not encounter an error when attempting to Marshal a Point to JSON")
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(res) != `{"lat":40.7486,"lng":-73.9864}` {
|
||||||
|
t.Error("Point should correctly Marshal to JSON")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enures that a point can be unmarhalled from JSON
|
||||||
|
func TestUnmarshalJSON(t *testing.T) {
|
||||||
|
data := []byte(`{"lat":40.7486,"lng":-73.9864}`)
|
||||||
|
p := &Point{}
|
||||||
|
err := p.UnmarshalJSON(data)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Should not encounter an error when attempting to Unmarshal a Point from JSON")
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.lat != 40.7486 || p.lng != -73.9864 {
|
||||||
|
t.Errorf("Point has mismatched data after Unmarshalling from JSON")
|
||||||
|
}
|
||||||
|
}
|
22
vendor/github.com/traetox/speedtest/LICENSE
generated
vendored
Normal file
22
vendor/github.com/traetox/speedtest/LICENSE
generated
vendored
Normal 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
vendor/github.com/traetox/speedtest/README.md
generated
vendored
Normal file
42
vendor/github.com/traetox/speedtest/README.md
generated
vendored
Normal 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
vendor/github.com/traetox/speedtest/main.go
generated
vendored
Normal file
220
vendor/github.com/traetox/speedtest/main.go
generated
vendored
Normal 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
vendor/github.com/traetox/speedtest/speedtestdotnet/actions.go
generated
vendored
Normal file
277
vendor/github.com/traetox/speedtest/speedtestdotnet/actions.go
generated
vendored
Normal 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
vendor/github.com/traetox/speedtest/speedtestdotnet/human.go
generated
vendored
Normal file
30
vendor/github.com/traetox/speedtest/speedtestdotnet/human.go
generated
vendored
Normal 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
vendor/github.com/traetox/speedtest/speedtestdotnet/servers.go
generated
vendored
Normal file
204
vendor/github.com/traetox/speedtest/speedtestdotnet/servers.go
generated
vendored
Normal 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 }
|
Loading…
Reference in New Issue
Block a user