├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── cmd ├── .gitignore ├── build.sh └── main.go ├── ping.go └── ping_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.exe~ 3 | *.dll 4 | *.so 5 | *.dylib 6 | *.test 7 | *.out 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.12.x 4 | 5 | os: 6 | - linux 7 | 8 | dist: trusty 9 | sudo: false 10 | 11 | install: true 12 | 13 | env: 14 | - GO111MODULE=on 15 | 16 | script: 17 | - go install ./... 18 | - ${GOPATH}/bin/cmd -aws -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Oğuzhan YILMAZ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CloudPing [![Build Status](https://travis-ci.org/c1982/cloudping.svg?branch=master)](https://travis-ci.org/c1982/cloudping) 2 | 3 | Cloud Ping is a command-line tool designed to estimate latency in AWS zones from your domain or server to which you are connected to the Internet. 4 | 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/c1982/cloudping)](https://goreportcard.com/report/github.com/c1982/cloudping) 6 | 7 | ### Download 8 | 9 | [https://github.com/c1982/cloudping/releases](https://github.com/c1982/cloudping/releases) 10 | 11 | ## Usage 12 | 13 | Check AWS endpoints latency of your location 14 | 15 | ```cping -aws``` 16 | 17 | Output: 18 | 19 | ``` 20 | Region: eu-central-1 Total: 584ms DNS Lookup: 113ms TCP Connection: 400ms First Byte Response: 69ms Content Transfer: 85.375µs 21 | Region: eu-west-3 Total: 605ms DNS Lookup: 127ms TCP Connection: 396ms First Byte Response: 81ms Content Transfer: 64.27µs 22 | Region: eu-north-1 Total: 612ms DNS Lookup: 126ms TCP Connection: 401ms First Byte Response: 83ms Content Transfer: 122.155µs 23 | Region: eu-west-2 Total: 614ms DNS Lookup: 127ms TCP Connection: 402ms First Byte Response: 84ms Content Transfer: 73.99µs 24 | Region: eu-west-1 Total: 648ms DNS Lookup: 114ms TCP Connection: 427ms First Byte Response: 106ms Content Transfer: 114.472µs 25 | Region: us-east-1 Total: 759ms DNS Lookup: 112ms TCP Connection: 487ms First Byte Response: 159ms Content Transfer: 121.658µs 26 | Region: ca-central-1 Total: 821ms DNS Lookup: 147ms TCP Connection: 504ms First Byte Response: 169ms Content Transfer: 73.301µs 27 | Region: us-east-2 Total: 857ms DNS Lookup: 138ms TCP Connection: 538ms First Byte Response: 180ms Content Transfer: 91.123µs 28 | Region: ap-south-1 Total: 880ms DNS Lookup: 134ms TCP Connection: 558ms First Byte Response: 187ms Content Transfer: 96.115µs 29 | Region: us-gov-east-1 Total: 963ms DNS Lookup: 214ms TCP Connection: 570ms First Byte Response: 178ms Content Transfer: 102.894µs 30 | Region: us-west-1 Total: 1057ms DNS Lookup: 127ms TCP Connection: 687ms First Byte Response: 242ms Content Transfer: 96.973µs 31 | Region: us-west-2 Total: 1082ms DNS Lookup: 114ms TCP Connection: 721ms First Byte Response: 245ms Content Transfer: 94.287µs 32 | Region: us-gov-west-1 Total: 1172ms DNS Lookup: 136ms TCP Connection: 790ms First Byte Response: 246ms Content Transfer: 108.831µs 33 | Region: sa-east-1 Total: 1296ms DNS Lookup: 113ms TCP Connection: 889ms First Byte Response: 293ms Content Transfer: 113.744µs 34 | Region: ap-southeast-1 Total: 1351ms DNS Lookup: 136ms TCP Connection: 914ms First Byte Response: 300ms Content Transfer: 48.492µs 35 | Region: ap-northeast-3 Total: 1509ms DNS Lookup: 136ms TCP Connection: 1034ms First Byte Response: 338ms Content Transfer: 67.812µs 36 | Region: cn-north-1 Total: 1521ms DNS Lookup: 270ms TCP Connection: 939ms First Byte Response: 310ms Content Transfer: 48.585µs 37 | Region: ap-northeast-1 Total: 1535ms DNS Lookup: 136ms TCP Connection: 1051ms First Byte Response: 347ms Content Transfer: 48.814µs 38 | Region: ap-northeast-2 Total: 1576ms DNS Lookup: 126ms TCP Connection: 1088ms First Byte Response: 360ms Content Transfer: 87.373µs 39 | Region: ap-southeast-2 Total: 1730ms DNS Lookup: 136ms TCP Connection: 1186ms First Byte Response: 407ms Content Transfer: 76.36µs 40 | Region: cn-northwest-1 Total: 1804ms DNS Lookup: 471ms TCP Connection: 1000ms First Byte Response: 332ms Content Transfer: 52.512µs 41 | ``` 42 | 43 | Show AWS best latency region 44 | 45 | ```cping -aws -best``` 46 | 47 | Output: 48 | 49 | ``` 50 | Region: eu-central-1 Total: 566ms DNS Lookup: 95ms TCP Connection: 392ms First Byte Response: 77ms Content Transfer: 88.74µs 51 | ``` 52 | 53 | Print CSV format 54 | 55 | ```cping -aws -csv``` 56 | 57 | Output: 58 | 59 | ``` 60 | eu-central-1,557.428034ms,86.129062ms,397.530328ms,73.61463ms,154.014µs 61 | ``` 62 | 63 | Test custom URL 64 | 65 | ```cping -url http://www.maestropanel.com``` 66 | 67 | ``` 68 | Region: unknown Total: 167ms DNS Lookup: 98ms TCP Connection: 30ms First Byte Response: 38ms Content Transfer: 265.611µs 69 | ``` 70 | 71 | ## Stats 72 | 73 | | Stats | Description | 74 | | ------ | ----------- | 75 | | Total | total time of all process | 76 | | DNS Lookup | resolution time of the requested domain | 77 | | TCP Connection | Time for TCP connection | 78 | | First Byte Response | First byte of response from server | 79 | | Content Transfer | Time to download the answer from the server | 80 | 81 | ## License 82 | 83 | Distributed under the MIT License. See `LICENSE` for more information. 84 | 85 | 86 | ## Inspired 87 | 88 | * https://cloudharmony.com/speedtest-for-aws 89 | * http://www.cloudping.info/ 90 | * http://www.cloudwatch.in/ 91 | * https://cloudharmony.com/speedtest-for-aws 92 | 93 | ## Contact 94 | 95 | Oğuzhan - [@c1982](https://twitter.com/c1982) - aspsrc@gmail.com 96 | 97 | Project Link: [https://github.com/c1982/cloudping](https://github.com/c1982/cloudping) -------------------------------------------------------------------------------- /cmd/.gitignore: -------------------------------------------------------------------------------- 1 | cping_* 2 | cmd -------------------------------------------------------------------------------- /cmd/build.sh: -------------------------------------------------------------------------------- 1 | ${HOME}/go/bin/gox -osarch="linux/amd64 windows/amd64 darwin/amd64" -output="cping_{{.OS}}_{{.Arch}}" -ldflags="-s -w" -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "time" 8 | 9 | "github.com/c1982/cloudping" 10 | ) 11 | 12 | var ( 13 | flagURL = flag.String("url", "", "for custom test URL. Default nil") 14 | flagMethod = flag.String("method", "GET", "HTTP method to use. Default GET") 15 | flagAWS = flag.Bool("aws", false, "Test all AWS regions enpoints. Default false") 16 | flagShowBest = flag.Bool("best", false, "Show only best region. Default false") 17 | flagCSV = flag.Bool("csv", false, "Output CSV format. Default false") 18 | responseTmp = `Region: %s Total: %7dms DNS Lookup: %7dms TCP Connection: %7dms First Byte Response: %7dms Content Transfer: %7s` 19 | responseTmpCSV = `%s,%s,%s,%s,%s,%s` 20 | responseTmpErr = `Error: %v` 21 | printusage = true 22 | ) 23 | 24 | func main() { 25 | 26 | flag.Parse() 27 | 28 | cp := cloudping.CloudPing{} 29 | 30 | if *flagURL != "" { 31 | urlPing := cp.Ping(*flagMethod, *flagURL) 32 | write(urlPing) 33 | printusage = false 34 | } 35 | 36 | if *flagAWS { 37 | list := cp.RunAWSTestAsync() 38 | 39 | for i := 0; i < len(list); i++ { 40 | write(list[i]) 41 | if *flagShowBest { 42 | if i == 0 { 43 | break 44 | } 45 | } 46 | } 47 | 48 | printusage = false 49 | } 50 | 51 | if printusage { 52 | usage() 53 | } 54 | } 55 | 56 | func write(p cloudping.PingItem) { 57 | 58 | if p.Err != nil { 59 | fmt.Printf(responseTmpErr, p.Err) 60 | return 61 | } 62 | 63 | if *flagCSV { 64 | fmt.Printf(responseTmpCSV+"\n", 65 | p.Region, 66 | p.TotalTime, 67 | p.DNSLookup, 68 | p.TCPConnection, 69 | p.FirstByteResponse, 70 | p.ContentTransfer) 71 | 72 | } else { 73 | 74 | fmt.Printf(responseTmp+"\n", 75 | p.Region, 76 | int(p.TotalTime/time.Millisecond), 77 | int(p.DNSLookup/time.Millisecond), 78 | int(p.TCPConnection/time.Millisecond), 79 | int(p.FirstByteResponse/time.Millisecond), 80 | p.ContentTransfer) 81 | } 82 | } 83 | 84 | func usage() { 85 | fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS]\n\n", os.Args[0]) 86 | fmt.Fprintln(os.Stderr, "OPTIONS:") 87 | flag.PrintDefaults() 88 | fmt.Fprintln(os.Stderr, "EXAMPLE:") 89 | fmt.Fprintln(os.Stderr, " cping -aws") 90 | fmt.Fprintln(os.Stderr, " cping -aws -best -csv") 91 | fmt.Fprintln(os.Stderr, " cping -url http://maestropanel.com") 92 | } 93 | -------------------------------------------------------------------------------- /ping.go: -------------------------------------------------------------------------------- 1 | package cloudping 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "net/http/httptrace" 7 | "sort" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | var ( 13 | awsendpoints map[string]string 14 | ) 15 | 16 | func init() { 17 | awsendpoints = map[string]string{ 18 | "us-east-1": "https://dynamodb.us-east-1.amazonaws.com/", 19 | "us-east-2": "https://dynamodb.us-east-2.amazonaws.com/", 20 | "us-west-1": "https://dynamodb.us-west-1.amazonaws.com/", 21 | "us-west-2": "https://dynamodb.us-west-2.amazonaws.com/", 22 | "ca-central-1": "https://dynamodb.ca-central-1.amazonaws.com/", 23 | "eu-west-1": "https://dynamodb.eu-west-1.amazonaws.com/", 24 | "eu-west-2": "https://dynamodb.eu-west-2.amazonaws.com/", 25 | "eu-central-1": "https://dynamodb.eu-central-1.amazonaws.com/", 26 | "eu-west-3": "https://dynamodb.eu-west-3.amazonaws.com/", 27 | "eu-north-1": "https://dynamodb.eu-north-1.amazonaws.com/", 28 | "ap-south-1": "https://dynamodb.ap-south-1.amazonaws.com/", 29 | "ap-northeast-3": "https://dynamodb.ap-northeast-3.amazonaws.com/", 30 | "ap-northeast-2": "https://dynamodb.ap-northeast-2.amazonaws.com/", 31 | "ap-southeast-1": "https://dynamodb.ap-southeast-1.amazonaws.com/", 32 | "ap-southeast-2": "https://dynamodb.ap-southeast-2.amazonaws.com/", 33 | "ap-northeast-1": "https://dynamodb.ap-northeast-1.amazonaws.com/", 34 | "sa-east-1": "https://dynamodb.sa-east-1.amazonaws.com/", 35 | "cn-north-1": "https://dynamodb.cn-north-1.amazonaws.com.cn/", 36 | "cn-northwest-1": "https://dynamodb.cn-northwest-1.amazonaws.com.cn/", 37 | "us-gov-east-1": "https://dynamodb.us-gov-east-1.amazonaws.com/", 38 | "us-gov-west-1": "https://dynamodb.us-gov-west-1.amazonaws.com/", 39 | } 40 | } 41 | 42 | //Pinglist structed of test results 43 | type Pinglist []PingItem 44 | 45 | func (pl Pinglist) Len() int { return len(pl) } 46 | func (pl Pinglist) Swap(i, j int) { pl[i], pl[j] = pl[j], pl[i] } 47 | func (pl Pinglist) Less(i, j int) bool { return pl[i].TotalTime < pl[j].TotalTime } 48 | 49 | //CloudPing main struct for logic 50 | type CloudPing struct { 51 | } 52 | 53 | //PingItem result of stats 54 | type PingItem struct { 55 | Region string 56 | URL string 57 | Err error 58 | DNSLookup time.Duration 59 | TCPConnection time.Duration 60 | FirstByteResponse time.Duration 61 | ContentTransfer time.Duration 62 | TotalTime time.Duration 63 | } 64 | 65 | //NewCloudPing create a new CloudPing object 66 | func NewCloudPing() *CloudPing { 67 | return &CloudPing{} 68 | } 69 | 70 | //Ping test single URL 71 | func (c *CloudPing) Ping(method, url string) PingItem { 72 | 73 | pi := c.do(method, url) 74 | pi.Region = "unknown" 75 | 76 | return pi 77 | } 78 | 79 | //RunAWSTestAsync test amazon dynamodb endpoints latency 80 | func (c *CloudPing) RunAWSTestAsync() Pinglist { 81 | 82 | var wg sync.WaitGroup 83 | var items = Pinglist{} 84 | 85 | for rg, u := range awsendpoints { 86 | 87 | wg.Add(1) 88 | 89 | go func(region, uri string, wag *sync.WaitGroup) { 90 | 91 | pi := c.do("GET", uri) 92 | pi.Region = region 93 | 94 | items = append(items, pi) 95 | 96 | wag.Done() 97 | 98 | }(rg, u, &wg) 99 | } 100 | 101 | wg.Wait() 102 | 103 | sort.Sort(items) 104 | return items 105 | } 106 | 107 | func (c *CloudPing) do(method, url string) (ping PingItem) { 108 | 109 | req, err := c.req(method, url) 110 | 111 | if err != nil { 112 | ping.Err = err 113 | return ping 114 | } 115 | 116 | ping = PingItem{} 117 | var dnsStartTime, dnsDoneTime time.Time 118 | var gotConnTime, gotFRB time.Time 119 | var readBody time.Time 120 | 121 | trace := &httptrace.ClientTrace{ 122 | DNSStart: func(_ httptrace.DNSStartInfo) { dnsStartTime = time.Now() }, 123 | DNSDone: func(_ httptrace.DNSDoneInfo) { dnsDoneTime = time.Now() }, 124 | GotConn: func(_ httptrace.GotConnInfo) { gotConnTime = time.Now() }, 125 | GotFirstResponseByte: func() { gotFRB = time.Now() }, 126 | } 127 | 128 | req = req.WithContext(httptrace.WithClientTrace(context.Background(), trace)) 129 | 130 | client := &http.Client{ 131 | Transport: c.transport(), 132 | CheckRedirect: func(req *http.Request, via []*http.Request) error { 133 | return http.ErrUseLastResponse 134 | }, 135 | } 136 | 137 | resp, err := client.Do(req) 138 | 139 | if err != nil { 140 | ping.Err = err 141 | return ping 142 | } 143 | 144 | resp.Body.Close() 145 | readBody = time.Now() 146 | 147 | if dnsStartTime.IsZero() { 148 | dnsStartTime = dnsDoneTime 149 | } 150 | 151 | ping.DNSLookup = dnsDoneTime.Sub(dnsStartTime) 152 | ping.TCPConnection = gotConnTime.Sub(dnsDoneTime) 153 | ping.FirstByteResponse = gotFRB.Sub(gotConnTime) 154 | ping.ContentTransfer = readBody.Sub(gotFRB) 155 | ping.TotalTime = readBody.Sub(dnsStartTime) 156 | 157 | return ping 158 | } 159 | 160 | func (c *CloudPing) req(method, url string) (req *http.Request, err error) { 161 | return http.NewRequest(method, url, nil) 162 | } 163 | 164 | func (c *CloudPing) transport() *http.Transport { 165 | 166 | tr := &http.Transport{ 167 | Proxy: http.ProxyFromEnvironment, 168 | MaxIdleConns: 100, 169 | IdleConnTimeout: 90 * time.Second, 170 | TLSHandshakeTimeout: 10 * time.Second, 171 | ExpectContinueTimeout: 1 * time.Second, 172 | } 173 | 174 | return tr 175 | } 176 | -------------------------------------------------------------------------------- /ping_test.go: -------------------------------------------------------------------------------- 1 | package cloudping 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func Test_Ping_Async(t *testing.T) { 9 | 10 | cp := NewCloudPing() 11 | list := cp.RunAWSTestAsync() 12 | 13 | for i := 0; i < len(list); i++ { 14 | 15 | p := list[i] 16 | 17 | if p.Err != nil { 18 | t.Error(p.Err) 19 | } 20 | 21 | fmt.Printf("Region: %s\tTotal: %s\tTCPtime: %s\tDNS Time: %s\tFirst Byte: %s\tDownload: %s\n", 22 | p.Region, 23 | p.TotalTime, 24 | p.TCPConnection, 25 | p.DNSLookup, 26 | p.FirstByteResponse, 27 | p.ContentTransfer) 28 | } 29 | } 30 | 31 | func Test_Single_Ping(t *testing.T) { 32 | cp := NewCloudPing() 33 | p := cp.Ping("GET", "http://maestropanel.com") 34 | 35 | if p.Err != nil { 36 | t.Error(p.Err) 37 | } 38 | 39 | fmt.Printf("Region: %s\tTotal: %s\tTCPtime: %s\tDNS Time: %s\tFirst Byte: %s\tDownload: %s\n", 40 | p.Region, 41 | p.TotalTime, 42 | p.TCPConnection, 43 | p.DNSLookup, 44 | p.FirstByteResponse, 45 | p.ContentTransfer) 46 | } 47 | --------------------------------------------------------------------------------