[
  {
    "path": ".dockerignore",
    "content": ".git\nLICENSE\nREADME.md\nDockerfile\nawsping\n"
  },
  {
    "path": ".github/workflows/goreleaser.yml",
    "content": "name: goreleaser\n\non:\n  push:\n    branches:\n      - \"!*\"\n    tags:\n      - \"v*.*.*\"\n\njobs:\n  goreleaser:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v2\n        with:\n          fetch-depth: 0\n\n      - name: Set up Go\n        uses: actions/setup-go@v2\n        with:\n          go-version: 1.17\n\n      - name: Login to Docker Hub\n        uses: docker/login-action@v1\n        with:\n          username: ${{ secrets.DOCKER_USERNAME }}\n          password: ${{ secrets.DOCKER_TOKEN }}\n\n      - name: Login to GitHub Container Registry\n        uses: docker/login-action@v1\n        with:\n          registry: ghcr.io\n          username: ${{ github.repository_owner }}\n          password: ${{ secrets.GH_PACKAGE_TOKEN }}\n\n      - name: Run GoReleaser\n        uses: goreleaser/goreleaser-action@v2\n        with:\n          version: latest\n          args: release --rm-dist\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/pr.yml",
    "content": "name: pr/push checks\n\non:\n  push:\n    branches:\n      - master\n    paths-ignore:\n      - '**/*.md'\n      - 'Makefile'\n  pull_request:\n    branches:\n      - master\n    paths-ignore:\n      - '**/*.md'\n      - 'Makefile'\n\njobs:\n\n  build:\n    name: Build, Test, Coverage\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v2\n\n    - name: Set up Go\n      uses: actions/setup-go@v2\n      with:\n        go-version: 1.17\n\n    - name: Lint\n      uses: golangci/golangci-lint-action@v2\n\n    - name: Build\n      run: go build -v ./...\n\n    - name: Test & Coverage\n      run: go test -v -coverprofile=coverage.out -covermode=atomic\n\n    - name: Upload coverage to Codecov\n      run: bash <(curl -s https://codecov.io/bash)\n"
  },
  {
    "path": ".gitignore",
    "content": "*.swp\nTODO.md\ndist/\n# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n\n# Folders\n_obj\n_test\n\n# Architecture specific extensions/prefixes\n*.[568vq]\n[568vq].out\n\n*.cgo1.go\n*.cgo2.c\n_cgo_defun.c\n_cgo_gotypes.go\n_cgo_export.*\n\n_testmain.go\n\n*.exe\n*.test\n*.prof\n\ncoverage.out\n"
  },
  {
    "path": ".goreleaser.yml",
    "content": "project_name: awsping\nbuilds:\n- env:\n  - CGO_ENABLED=0\n  main: ./cmd/awsping\n  goos:\n    - linux\n    - windows\n    - darwin\n  goarch:\n    - amd64\nchecksum:\n  name_template: 'checksums.txt'\nsnapshot:\n  name_template: \"{{ .Tag }}-next\"\nchangelog:\n  sort: asc\n  filters:\n    exclude:\n    - '^docs:'\n    - '^test:'\n    - '^Makefile:'\n    - '^README:'\n    - '^gitignore:'\n    - '^goreleaser:'\ndockers:\n  - image_templates:\n    - \"docker.io/evkalinin/{{.ProjectName}}:{{ .Tag }}\"\n    - \"docker.io/evkalinin/{{.ProjectName}}:latest\"\n    - \"ghcr.io/ekalinin/{{.ProjectName}}:{{ .Tag }}\"\n    - \"ghcr.io/ekalinin/{{.ProjectName}}:latest\"\n    dockerfile: Dockerfile.goreleaser\n    build_flag_templates:\n    - \"--pull\"\n    - \"--label=org.opencontainers.image.created={{.Date}}\"\n    - \"--label=org.opencontainers.image.name={{.ProjectName}}\"\n    - \"--label=org.opencontainers.image.revision={{.FullCommit}}\"\n    - \"--label=org.opencontainers.image.version={{.Version}}\"\n    - \"--label=org.opencontainers.image.source={{.GitURL}}\"\n    - \"--platform=linux/amd64\"\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM golang:1.17-bullseye as build\nCOPY . /build\nWORKDIR /build\nRUN make\n\nFROM gcr.io/distroless/base\nCOPY --from=build /build/awsping /\n\nENTRYPOINT [\"/awsping\"]\n"
  },
  {
    "path": "Dockerfile.goreleaser",
    "content": "FROM gcr.io/distroless/base\nCOPY awsping /\n\nENTRYPOINT [\"/awsping\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2016 Eugene Kalinin\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "NAME=awsping\nEXEC=${NAME}\nBUILD_DIR=build\nBUILD_OS=\"windows darwin freebsd linux\"\nBUILD_ARCH=\"amd64 386\"\nBUILD_DIR=build\nSRC_CMD=cmd/awsping/main.go\nVERSION=`grep \"Version\" utils.go | grep -o -E '[0-9]\\.[0-9]\\.[0-9]{1,2}'`\n\nbuild:\n\tgo build -race -o ${EXEC} ${SRC_CMD}\n\nclean:\n\t@rm -f ${EXEC}\n\t@rm -f ${BUILD_DIR}/*\n\t@go clean\n\n#\n# Tests, linters\n#\n\nlint:\n\tgolint\n\n# make run ARGS=\"-h\"\nrun:\n\tgo run cmd/awsping/main.go $(ARGS)\n\ntest: lint\n\t@go test -cover .\n\n#\n# Release\n#\ncheck-version:\nifdef VERSION\n\t@echo Current version: $(VERSION)\nelse\n\t$(error VERSION is not set)\nendif\n\ncheck-master:\nifneq ($(shell git rev-parse --abbrev-ref HEAD),master)\n\t$(error You're not on the \"master\" branch)\nendif\n\nrelease: check-master check-version\n\tgit tag v${VERSION} && \\\n\tgit push origin v${VERSION}\n\nrelease-test: check-master check-version\n\tgoreleaser release --snapshot --rm-dist\n\nbuildall: clean\n\t@mkdir -p ${BUILD_DIR}\n\t@for os in \"${BUILD_OS}\" ; do \\\n\t\tfor arch in \"${BUILD_ARCH}\" ; do \\\n\t\t\techo \" * build $$os for $$arch\"; \\\n\t\t\tGOOS=$$os GOARCH=$$arch go build -ldflags \"-s\" -o ${BUILD_DIR}/${EXEC} ${SRC_CMD}; \\\n\t\t\tcd ${BUILD_DIR}; \\\n\t\t\ttar czf ${EXEC}.$$os.$$arch.tgz ${EXEC}; \\\n\t\t\tcd - ; \\\n\t\tdone done\n\t@rm ${BUILD_DIR}/${EXEC}\n\n#\n# Docker\n#\n\ndocker:\n\tdocker build -t awsping .\n\ndocker-run: docker\n\tdocker run awsping -verbose 2 -repeats 2\n"
  },
  {
    "path": "README.md",
    "content": "# awsping\nConsole tool to check the latency to each AWS region\n\n[![Go Report Card](https://goreportcard.com/badge/github.com/ekalinin/awsping)](https://goreportcard.com/report/github.com/ekalinin/awsping)\n[![codecov](https://codecov.io/gh/ekalinin/awsping/branch/master/graph/badge.svg)](https://codecov.io/gh/ekalinin/awsping)\n[![Go Reference](https://pkg.go.dev/badge/github.com/ekalinin/awsping.svg)](https://pkg.go.dev/github.com/ekalinin/awsping)\n[![MIT license](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://opensource.org/licenses/MIT)\n![GitHub release (latest by date)](https://img.shields.io/github/v/release/ekalinin/awsping)\n\n# ToC\n\n* [Usage](#usage)\n  * [Test via TCP](#test-via-tcp)\n  * [Test via HTTP](#test-via-http)\n  * [Test via HTTPS](#test-via-https)\n  * [Test several times](#test-several-times)\n  * [Verbose mode](#verbose-mode)\n  * [Get Help](#get-help)\n* [Get binary file](#get-binary-file)\n* [Build from sources](#build-from-sources)\n* [Use with Docker](#use-with-docker)\n  * [Build a Docker image](#build-a-docker-image)\n  * [Run the Docker image](#run-the-docker-image)\n\n# Usage\n\n## Test via TCP\n\n```bash\n➥ ./awsping\nEurope (Frankfurt)                    51.86 ms\nEurope (Ireland)                      62.86 ms\nUS-East (Virginia)                   126.39 ms\nUS-East (Ohio)                       154.81 ms\nAsia Pacific (Mumbai)                181.09 ms\nUS-West (California)                 194.27 ms\nUS-West (Oregon)                     211.87 ms\nSouth America (São Paulo)            246.20 ms\nAsia Pacific (Tokyo)                 309.27 ms\nAsia Pacific (Seoul)                 322.76 ms\nAsia Pacific (Sydney)                346.37 ms\nAsia Pacific (Singapore)             407.91 ms\n```\n\n## Test via HTTP\n\n```bash\n➥ ./awsping -http\nEurope (Frankfurt)                   222.56 ms\nEurope (Ireland)                     226.76 ms\nUS-East (Virginia)                   349.17 ms\nUS-West (California)                 488.12 ms\nUS-East (Ohio)                       513.69 ms\nAsia Pacific (Mumbai)                528.51 ms\nUS-West (Oregon)                     532.05 ms\nSouth America (São Paulo)            599.36 ms\nAsia Pacific (Seoul)                 715.92 ms\nAsia Pacific (Sydney)                721.47 ms\nAsia Pacific (Tokyo)                 745.24 ms\nAsia Pacific (Singapore)             847.36 ms\n```\n\n## Test via HTTPS\n\n```bash\n➥ ./awsping -https\nEurope (Stockholm)                   216.67 ms\nEurope (Frankfurt)                   263.20 ms\nEurope (Paris)                       284.32 ms\nEurope (Milan)                       305.63 ms\nEurope (Ireland)                     327.34 ms\nEurope (London)                      332.17 ms\nMiddle East (Bahrain)                590.74 ms\nUS-East (N. Virginia)                595.13 ms\nCanada (Central)                     628.44 ms\nUS-East (Ohio)                       635.32 ms\nAsia Pacific (Mumbai)                755.56 ms\nAsia Pacific (Hong Kong)             843.90 ms\nUS-West (N. California)              870.65 ms\nAsia Pacific (Singapore)             899.50 ms\nAfrica (Cape Town)                   912.06 ms\nUS-West (Oregon)                     919.34 ms\nSouth America (São Paulo)            985.93 ms\nAsia Pacific (Tokyo)                1122.67 ms\nAsia Pacific (Seoul)                1138.76 ms\nAsia Pacific (Osaka)                1167.40 ms\nAsia Pacific (Sydney)               1328.90 ms\n```\n\n## Test several times\n\n```bash\n➥ ./awsping -repeats 3\nEurope (Frankfurt)                    50.13 ms\nEurope (Ireland)                      62.67 ms\nUS-East (Virginia)                   126.88 ms\nUS-East (Ohio)                       155.37 ms\nUS-West (California)                 195.75 ms\nUS-West (Oregon)                     206.19 ms\nAsia Pacific (Mumbai)                222.34 ms\nSouth America (São Paulo)            254.28 ms\nAsia Pacific (Tokyo)                 308.52 ms\nAsia Pacific (Seoul)                 325.93 ms\nAsia Pacific (Sydney)                349.62 ms\nAsia Pacific (Singapore)             378.53 ms\n```\n\n## Verbose mode\n\n```bash\n➥ ./awsping -repeats 3 -verbose 1\n      Code            Region                                      Latency\n    0 eu-central-1    Europe (Frankfurt)                         47.39 ms\n    1 eu-west-1       Europe (Ireland)                           62.28 ms\n    2 us-east-1       US-East (Virginia)                        128.45 ms\n    3 us-east-2       US-East (Ohio)                            155.53 ms\n    4 us-west-1       US-West (California)                      194.37 ms\n    5 us-west-2       US-West (Oregon)                          208.91 ms\n    6 ap-south-1      Asia Pacific (Mumbai)                     226.59 ms\n    7 sa-east-1       South America (São Paulo)                 254.67 ms\n    8 ap-northeast-1  Asia Pacific (Tokyo)                      301.97 ms\n    9 ap-northeast-2  Asia Pacific (Seoul)                      323.10 ms\n   10 ap-southeast-2  Asia Pacific (Sydney)                     341.26 ms\n   11 ap-southeast-1  Asia Pacific (Singapore)                  397.47 ms\n```\n\n```bash\n➥ ./awsping -repeats 3 -verbose 2\n      Code            Region                             Try #1          Try #2          Try #3     Avg Latency\n    0 eu-central-1    Europe (Frankfurt)               45.18 ms        45.46 ms        45.68 ms        45.44 ms\n    1 eu-west-1       Europe (Ireland)                 61.89 ms        62.99 ms        62.98 ms        62.62 ms\n    2 us-east-1       US-East (Virginia)              125.15 ms       126.75 ms       126.49 ms       126.13 ms\n    3 us-east-2       US-East (Ohio)                  154.05 ms       154.28 ms       153.53 ms       153.96 ms\n    4 us-west-1       US-West (California)            196.20 ms       195.05 ms       193.76 ms       195.00 ms\n    5 us-west-2       US-West (Oregon)                204.04 ms       203.97 ms       203.84 ms       203.95 ms\n    6 ap-south-1      Asia Pacific (Mumbai)           175.27 ms       300.68 ms       172.18 ms       216.05 ms\n    7 sa-east-1       South America (São Paulo)       243.48 ms       247.12 ms       248.32 ms       246.31 ms\n    8 ap-northeast-1  Asia Pacific (Tokyo)            324.78 ms       312.70 ms       319.02 ms       318.83 ms\n    9 ap-northeast-2  Asia Pacific (Seoul)            328.96 ms       327.65 ms       326.17 ms       327.59 ms\n   10 ap-southeast-2  Asia Pacific (Sydney)           388.17 ms       347.74 ms       393.58 ms       376.50 ms\n   11 ap-southeast-1  Asia Pacific (Singapore)        409.53 ms       403.61 ms       405.84 ms       406.33 ms\n```\n\n## Get Help\n\n```bash\n➜ ./awsping -h\nUsage of ./awsping:\n  -http\n    \tUse http transport (default is tcp)\n  -https\n    \tUse https transport (default is tcp)\n  -list-regions\n    \tShow list of regions\n  -repeats int\n    \tNumber of repeats (default 1)\n  -service string\n    \tAWS Service: ec2, sdb, sns, sqs, ... (default \"dynamodb\")\n  -v\tShow version\n  -verbose int\n    \tVerbosity level\n```\n\n# Get binary file\n\n```bash\n$ wget https://github.com/ekalinin/awsping/releases/download/0.5.2/awsping.linux.amd64.tgz\n$ tar xzvf awsping.linux.amd64.tgz\n$ chmod +x awsping\n$ ./awsping -v\n0.5.2\n```\n\n# Build from sources\n\n```bash\n➥ make build\n```\n\n# Use with Docker\n## Build a Docker image\n\n```\n$ docker build -t awsping .\n```\n\n## Run the Docker image\n```\n$ docker run --rm awsping\n```\n\nArguments can be used as mentioned in the _Usage_ section.\n\ni.e.:\n```\n$ docker run --rm awsping -repeats 3 -verbose 2\n```\n"
  },
  {
    "path": "aws.go",
    "content": "package awsping\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n)\n\n// CheckType describes a type for a check\ntype CheckType int\n\nconst (\n\t// CheckTypeTCP is TCP type of check\n\tCheckTypeTCP CheckType = iota\n\t// CheckTypeHTTP is HTTP type of check\n\tCheckTypeHTTP\n\t// CheckTypeHTTPS is HTTPS type of check\n\tCheckTypeHTTPS\n)\n\n// --------------------------------------------\n\n// AWSRegion description of the AWS EC2 region\ntype AWSRegion struct {\n\tName      string\n\tCode      string\n\tService   string\n\tLatencies []time.Duration\n\tError     error\n\tCheckType CheckType\n\n\tTarget  Targetter\n\tRequest Requester\n}\n\n// NewRegion creates a new region with a name and code\nfunc NewRegion(name, code string) AWSRegion {\n\treturn AWSRegion{\n\t\tName:      name,\n\t\tCode:      code,\n\t\tCheckType: CheckTypeTCP,\n\t\tRequest:   NewAWSRequest(),\n\t}\n}\n\n// CheckLatency does a latency check for a region\nfunc (r *AWSRegion) CheckLatency(wg *sync.WaitGroup) {\n\tdefer wg.Done()\n\n\tif r.CheckType == CheckTypeHTTP || r.CheckType == CheckTypeHTTPS {\n\t\tr.checkLatencyHTTP(r.CheckType == CheckTypeHTTPS)\n\t} else {\n\t\tr.checkLatencyTCP()\n\t}\n}\n\n// checkLatencyHTTP Test Latency via HTTP\nfunc (r *AWSRegion) checkLatencyHTTP(https bool) {\n\turl := r.Target.GetURL()\n\tl, err := r.Request.Do(useragent, url, RequestTypeHTTP)\n\tif err != nil {\n\t\tr.Error = err\n\t\treturn\n\t}\n\tr.Latencies = append(r.Latencies, l)\n}\n\n// checkLatencyTCP Test Latency via TCP\nfunc (r *AWSRegion) checkLatencyTCP() {\n\ttcpAddr, err := r.Target.GetIP()\n\tif err != nil {\n\t\tr.Error = err\n\t\treturn\n\t}\n\n\tl, err := r.Request.Do(useragent, tcpAddr.String(), RequestTypeTCP)\n\tif err != nil {\n\t\tr.Error = err\n\t\treturn\n\t}\n\tr.Latencies = append(r.Latencies, l)\n}\n\n// GetLatency returns Latency in ms\nfunc (r *AWSRegion) GetLatency() float64 {\n\tsum := float64(0)\n\tfor _, l := range r.Latencies {\n\t\tsum += Duration2ms(l)\n\t}\n\treturn sum / float64(len(r.Latencies))\n}\n\n// GetLatencyStr returns Latency in string\nfunc (r *AWSRegion) GetLatencyStr() string {\n\tif r.Error != nil {\n\t\treturn r.Error.Error()\n\t}\n\treturn fmt.Sprintf(\"%.2f ms\", r.GetLatency())\n}\n\n// --------------------------------------------\n\n// AWSRegions slice of the AWSRegion\ntype AWSRegions []AWSRegion\n\n// Len returns a count of regions\nfunc (rs AWSRegions) Len() int {\n\treturn len(rs)\n}\n\n// Less return a result of latency compare between two regions\nfunc (rs AWSRegions) Less(i, j int) bool {\n\treturn rs[i].GetLatency() < rs[j].GetLatency()\n}\n\n// Swap two regions by index\nfunc (rs AWSRegions) Swap(i, j int) {\n\trs[i], rs[j] = rs[j], rs[i]\n}\n\n// SetService sets service for all regions\nfunc (rs AWSRegions) SetService(service string) {\n\tfor i := range rs {\n\t\trs[i].Service = service\n\t}\n}\n\n// SetCheckType sets Check Type for all regions\nfunc (rs AWSRegions) SetCheckType(checkType CheckType) {\n\tfor i := range rs {\n\t\trs[i].CheckType = checkType\n\t}\n}\n\n// SetDefaultTarget sets default target instance\nfunc (rs AWSRegions) SetDefaultTarget() {\n\trs.SetTarget(func(r *AWSRegion) {\n\t\tr.Target = &AWSTarget{\n\t\t\tHTTPS:   r.CheckType == CheckTypeHTTPS,\n\t\t\tCode:    r.Code,\n\t\t\tService: r.Service,\n\t\t\tRnd:     mkRandomString(13),\n\t\t}\n\t})\n}\n\n// SetTarget sets default target instance for all regions\nfunc (rs AWSRegions) SetTarget(fn func(r *AWSRegion)) {\n\tfor i := range rs {\n\t\tfn(&rs[i])\n\t}\n}\n"
  },
  {
    "path": "aws_test.go",
    "content": "package awsping\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestAWSRegionError(t *testing.T) {\n\tAWSErr := errors.New(\"something bad\")\n\tr := AWSRegion{Error: AWSErr}\n\n\tgot := r.GetLatencyStr()\n\twant := AWSErr.Error()\n\n\tif got != want {\n\t\tt.Errorf(\"failed:\\ngot=%q\\nwant=%q\", got, want)\n\t}\n}\n\ntype testTarget struct {\n\tURL string\n\tIP  *net.TCPAddr\n}\n\nfunc (r *testTarget) GetURL() string {\n\treturn r.URL\n}\n\n// GetIP return IP for AWS target\nfunc (r *testTarget) GetIP() (*net.TCPAddr, error) {\n\treturn r.IP, nil\n}\n\nfunc TestAWSRegionCheckLatencyHTTP(t *testing.T) {\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\ttime.Sleep(15 * time.Millisecond)\n\t\tfmt.Fprintln(w, \"X\")\n\t}))\n\tdefer ts.Close()\n\n\ttt := testTarget{URL: ts.URL}\n\n\tregions := GetRegions()\n\tservice := \"ec2\"\n\tcheckType := CheckTypeHTTP\n\n\tregions.SetService(service)\n\tregions.SetCheckType(checkType)\n\tregions.SetTarget(func(r *AWSRegion) {\n\t\tr.Target = &tt\n\t})\n\n\tvar wg sync.WaitGroup\n\twg.Add(1)\n\tregions[0].CheckLatency(&wg)\n\n\tgot := regions[0].GetLatency()\n\twant := 15.0\n\n\tif got < want || got > want*2 {\n\t\tt.Errorf(\"failed:\\ngot=%f\\nwant=%f\", got, want)\n\t}\n\n\t// check \"error\"\n\terrTxt := \"something bad\"\n\tregions[0].Request = &testRequest{err: errors.New(errTxt)}\n\n\twg.Add(1)\n\tregions[0].CheckLatency(&wg)\n\n\tif regions[0].Error == nil {\n\t\tt.Errorf(\"failed: error should not be empty\")\n\t}\n\n\tif regions[0].Error.Error() != errTxt {\n\t\tt.Errorf(\"failed: error should be empty=%s\", errTxt)\n\t}\n}\n\ntype testRequest struct {\n\tduration time.Duration\n\terr      error\n}\n\nfunc (d *testRequest) Do(_, _ string, _ RequestType) (time.Duration, error) {\n\tif d.err != nil {\n\t\treturn 0, d.err\n\t}\n\treturn d.duration, nil\n}\n\nfunc TestAWSRegionCheckLatencyTCP(t *testing.T) {\n\t// just random local IP\n\ttt := testTarget{IP: &net.TCPAddr{\n\t\tIP:   net.IPv4(127, 0, 0, 1),\n\t\tPort: 67890,\n\t}}\n\n\tregions := GetRegions()\n\tservice := \"ec2\"\n\tcheckType := CheckTypeTCP\n\n\tregions.SetService(service)\n\tregions.SetCheckType(checkType)\n\tregions.SetTarget(func(r *AWSRegion) {\n\t\tr.Target = &tt\n\t})\n\tregions[0].Request = &testRequest{duration: 15 * time.Millisecond}\n\n\tvar wg sync.WaitGroup\n\twg.Add(1)\n\tregions[0].CheckLatency(&wg)\n\n\tgot := regions[0].GetLatency()\n\twant := 15.0\n\n\tif got < want || got > want+1 {\n\t\tt.Errorf(\"failed:\\ngot=%f\\nwant=%f\\nregion=%q\", got, want, regions[0])\n\t}\n\n\tif regions[0].Error != nil {\n\t\tt.Errorf(\"failed: error should be empty\")\n\t}\n\n\t// check \"error\"\n\terrTxt := \"something bad\"\n\tregions[0].Request = &testRequest{err: errors.New(errTxt)}\n\n\twg.Add(1)\n\tregions[0].CheckLatency(&wg)\n\n\tif regions[0].Error == nil {\n\t\tt.Errorf(\"failed: error should not be empty\")\n\t}\n\n\tif regions[0].Error.Error() != errTxt {\n\t\tt.Errorf(\"failed: error should be empty=%s\", errTxt)\n\t}\n}\n\n// ---------------------------------------------\n\nfunc TestAWSRegionsLen(t *testing.T) {\n\tregions := GetRegions()\n\n\tgot := regions.Len()\n\twant := len(regions)\n\n\tif got != want {\n\t\tt.Errorf(\"failed:\\ngot=%q\\nwant=%q\", got, want)\n\t}\n}\n\nfunc TestAWSRegionsLess(t *testing.T) {\n\tregions := GetRegions()\n\n\tregions[0].Latencies = []time.Duration{15 * time.Millisecond}\n\tregions[1].Latencies = []time.Duration{25 * time.Millisecond}\n\n\tif !regions.Less(0, 1) {\n\t\tt.Errorf(\"failed: not less, regions=%q\", regions)\n\t}\n}\n\nfunc TestAWSRegionsSwap(t *testing.T) {\n\tregions := GetRegions()\n\n\tregions[0].Latencies = []time.Duration{15 * time.Millisecond}\n\tregions[1].Latencies = []time.Duration{25 * time.Millisecond}\n\n\tregions.Swap(0, 3)\n\n\tif len(regions[0].Latencies) != 0 {\n\t\tt.Errorf(\"failed: not swapped, regions=%q\", regions)\n\t}\n}\n\nfunc TestAWSRegionsSetService(t *testing.T) {\n\tregions := GetRegions()\n\tservice := \"ec2\"\n\n\tregions.SetService(service)\n\n\tif regions[0].Service != service || regions[len(regions)-1].Service != service {\n\t\tt.Errorf(\"failed: not set, regions=%q, service=%s\", regions, service)\n\t}\n}\n\nfunc TestAWSRegionsSetCheckType(t *testing.T) {\n\tregions := GetRegions()\n\tcheckType := CheckTypeHTTP\n\n\tregions.SetCheckType(checkType)\n\n\tif regions[0].CheckType != checkType || regions[len(regions)-1].CheckType != checkType {\n\t\tt.Errorf(\"failed: not set, regions=%q, checkType=%d\", regions, checkType)\n\t}\n}\n\nfunc TestAWSRegionsSetDefaultTarget(t *testing.T) {\n\tregions := GetRegions()\n\tservice := \"ec2\"\n\tcheckType := CheckTypeHTTPS\n\n\tregions.SetService(service)\n\tregions.SetCheckType(checkType)\n\tregions.SetDefaultTarget()\n\n\tgot := regions[0].Target.GetURL()\n\twant := fmt.Sprintf(\"https://ec2.%s.amazonaws.com/ping?x=\", regions[0].Code)\n\n\tif !strings.HasPrefix(got, want) {\n\t\tt.Errorf(\"failed: wrong url\\ngot=%s\\nneed=%s\", got, want)\n\t}\n}\n"
  },
  {
    "path": "cmd/awsping/main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/ekalinin/awsping\"\n)\n\nvar (\n\trepeats     = flag.Int(\"repeats\", 1, \"Number of repeats\")\n\tuseHTTP     = flag.Bool(\"http\", false, \"Use http transport (default is tcp)\")\n\tuseHTTPS    = flag.Bool(\"https\", false, \"Use https transport (default is tcp)\")\n\tshowVer     = flag.Bool(\"v\", false, \"Show version\")\n\tverbose     = flag.Int(\"verbose\", 0, \"Verbosity level (0: name-latency); 1: code-name-latency; 2: code-name-tries-avg\")\n\tservice     = flag.String(\"service\", \"dynamodb\", \"AWS Service: ec2, sdb, sns, sqs, ...\")\n\tlistRegions = flag.Bool(\"list-regions\", false, \"Show list of regions\")\n)\n\nfunc main() {\n\tflag.Parse()\n\n\tif *showVer {\n\t\tfmt.Println(awsping.Version)\n\t\tos.Exit(0)\n\t}\n\n\tregions := awsping.GetRegions()\n\n\tif *listRegions {\n\t\tlo := awsping.NewOutput(awsping.ShowOnlyRegions, 0)\n\t\tlo.Show(&regions)\n\t\tos.Exit(0)\n\t}\n\n\trand.Seed(time.Now().UnixNano())\n\n\tawsping.CalcLatency(regions, *repeats, *useHTTP, *useHTTPS, *service)\n\tlo := awsping.NewOutput(*verbose, *repeats)\n\tlo.Show(&regions)\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/ekalinin/awsping\n\ngo 1.17\n"
  },
  {
    "path": "request.go",
    "content": "package awsping\n\nimport (\n\t\"net\"\n\t\"net/http\"\n\t\"time\"\n)\n\n// RequestType describes a type for a request type\ntype RequestType int\n\nconst (\n\t// RequestTypeHTTP is HTTP type of request\n\tRequestTypeHTTP RequestType = iota\n\t// RequestTypeTCP is TCP type of request\n\tRequestTypeTCP\n)\n\n// Requester is an interface to do a network request\ntype Requester interface {\n\tDo(ua, url string, reqType RequestType) (time.Duration, error)\n}\n\n// AWSHTTPRequester is an interface for HTTP requests\ntype AWSHTTPRequester interface {\n\tDo(req *http.Request) (*http.Response, error)\n}\n\n// AWSTCPRequester is an interface for TCP requests\ntype AWSTCPRequester interface {\n\tDial(network, address string) (net.Conn, error)\n}\n\n// AWSRequest implements Requester interface\ntype AWSRequest struct {\n\thttpClient AWSHTTPRequester\n\ttcpClient  AWSTCPRequester\n}\n\n// NewAWSRequest creates a new instance of AWSRequest\nfunc NewAWSRequest() *AWSRequest {\n\treturn &AWSRequest{\n\t\thttpClient: &http.Client{},\n\t\ttcpClient:  &net.Dialer{},\n\t}\n}\n\n// DoHTTP does HTTP request for a URL by User-Agent (ua)\nfunc (r *AWSRequest) DoHTTP(ua, url string) (time.Duration, error) {\n\treq, err := http.NewRequest(\"GET\", url, nil)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treq.Header.Set(\"User-Agent\", ua)\n\n\tstart := time.Now()\n\tresp, err := r.httpClient.Do(req)\n\tlatency := time.Since(start)\n\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tdefer resp.Body.Close()\n\n\treturn latency, nil\n}\n\n// DoTCP does TCP request to the Addr\nfunc (r *AWSRequest) DoTCP(_, addr string) (time.Duration, error) {\n\tstart := time.Now()\n\tconn, err := r.tcpClient.Dial(\"tcp\", addr)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tl := time.Since(start)\n\tdefer conn.Close()\n\n\treturn l, nil\n}\n\n// Do does a request. Type of request depends on reqType\nfunc (r *AWSRequest) Do(ua, url string, reqType RequestType) (time.Duration, error) {\n\tif reqType == RequestTypeHTTP {\n\t\treturn r.DoHTTP(ua, url)\n\t}\n\treturn r.DoTCP(ua, url)\n}\n"
  },
  {
    "path": "request_test.go",
    "content": "package awsping\n\nimport (\n\t\"errors\"\n\t\"net\"\n\t\"net/http\"\n\t\"testing\"\n)\n\ntype testTCPClient struct {\n\terr error\n}\n\nfunc (c *testTCPClient) Dial(n, a string) (net.Conn, error) {\n\tif c.err != nil {\n\t\treturn nil, c.err\n\t}\n\tvar con net.Conn\n\treturn con, nil\n}\n\ntype testHTTPClient struct {\n\terr error\n}\n\nfunc (c *testHTTPClient) Do(r *http.Request) (*http.Response, error) {\n\tif c.err != nil {\n\t\treturn nil, c.err\n\t}\n\treturn &http.Response{}, nil\n}\n\nfunc TestRequestDoTCPError(t *testing.T) {\n\n\tr := &AWSRequest{\n\t\ttcpClient: &testTCPClient{\n\t\t\terr: net.ErrWriteToConnected,\n\t\t},\n\t}\n\n\tl, err := r.DoTCP(\"net\", \"some-addr\")\n\tif err == nil {\n\t\tt.Errorf(\"Error should not be empty\")\n\t}\n\tif !errors.Is(err, net.ErrWriteToConnected) {\n\t\tt.Errorf(\"Want=%v, got=%v\", net.ErrWriteToConnected, err)\n\t}\n\tif l != 0 {\n\t\tt.Errorf(\"Latency for error should be 0, but got=%d\", l)\n\t}\n}\n\nfunc TestDoErr(t *testing.T) {\n\terrTCP := errors.New(\"error from tcp\")\n\terrHTTP := errors.New(\"error from http\")\n\n\tr := &AWSRequest{\n\t\ttcpClient: &testTCPClient{\n\t\t\terr: errTCP,\n\t\t},\n\t\thttpClient: &testHTTPClient{\n\t\t\terr: errHTTP,\n\t\t},\n\t}\n\n\tl, err := r.Do(\"ua\", \"addr\", RequestTypeTCP)\n\tif err == nil {\n\t\tt.Errorf(\"Error should not be empty\")\n\t}\n\tif !errors.Is(err, errTCP) {\n\t\tt.Errorf(\"Want=%v, got=%v\", errTCP, err)\n\t}\n\tif l != 0 {\n\t\tt.Errorf(\"Latency for error should be 0, but got=%d\", l)\n\t}\n\n\tl, err = r.Do(\"ua\", \"addr\", RequestTypeHTTP)\n\tif err == nil {\n\t\tt.Errorf(\"Error should not be empty\")\n\t}\n\tif !errors.Is(err, errHTTP) {\n\t\tt.Errorf(\"Want=%v, got=%v\", errHTTP, err)\n\t}\n\tif l != 0 {\n\t\tt.Errorf(\"Latency for error should be 0, but got=%d\", l)\n\t}\n}\n"
  },
  {
    "path": "target.go",
    "content": "package awsping\n\nimport (\n\t\"fmt\"\n\t\"net\"\n)\n\n// Targetter is an interface to get target's IP or URL\ntype Targetter interface {\n\tGetURL() string\n\tGetIP() (*net.TCPAddr, error)\n}\n\n// AWSTarget implements Targetter for AWS\ntype AWSTarget struct {\n\tHTTPS   bool\n\tCode    string\n\tService string\n\tRnd     string\n}\n\n// GetURL return URL for AWS target\nfunc (r *AWSTarget) GetURL() string {\n\tproto := \"http\"\n\tif r.HTTPS {\n\t\tproto = \"https\"\n\t}\n\thostname := fmt.Sprintf(\"%s.%s.amazonaws.com\", r.Service, r.Code)\n\turl := fmt.Sprintf(\"%s://%s/ping?x=%s\", proto, hostname, r.Rnd)\n\treturn url\n}\n\n// GetIP return IP for AWS target\nfunc (r *AWSTarget) GetIP() (*net.TCPAddr, error) {\n\ttcpURI := fmt.Sprintf(\"%s.%s.amazonaws.com:80\", r.Service, r.Code)\n\treturn net.ResolveTCPAddr(\"tcp4\", tcpURI)\n}\n"
  },
  {
    "path": "utils.go",
    "content": "package awsping\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"math/rand\"\n\t\"os\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n)\n\nvar (\n\t// Version describes application version\n\tVersion   = \"2.0.0\"\n\tgithub    = \"https://github.com/ekalinin/awsping\"\n\tuseragent = fmt.Sprintf(\"AwsPing/%s (+%s)\", Version, github)\n)\n\nconst (\n\t// ShowOnlyRegions describes a type of output when only region's name and code printed out\n\tShowOnlyRegions = -1\n)\n\nvar letterRunes = []rune(\"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\")\n\n// Duration2ms converts time.Duration to ms (float64)\nfunc Duration2ms(d time.Duration) float64 {\n\treturn float64(d.Nanoseconds()) / 1000 / 1000\n}\n\n// mkRandomString returns random string\nfunc mkRandomString(n int) string {\n\tb := make([]rune, n)\n\tfor i := range b {\n\t\tb[i] = letterRunes[rand.Intn(len(letterRunes))]\n\t}\n\treturn string(b)\n}\n\n// LatencyOutput prints data into console\ntype LatencyOutput struct {\n\tLevel   int\n\tRepeats int\n\tw       io.Writer\n}\n\n// NewOutput creates a new LatencyOutput instance\nfunc NewOutput(level, repeats int) *LatencyOutput {\n\treturn &LatencyOutput{\n\t\tLevel:   level,\n\t\tRepeats: repeats,\n\t\tw:       os.Stdout,\n\t}\n}\n\nfunc (lo *LatencyOutput) show(regions *AWSRegions) {\n\tfor _, r := range *regions {\n\t\tfmt.Fprintf(lo.w, \"%-15s %-s\\n\", r.Code, r.Name)\n\t}\n}\n\nfunc (lo *LatencyOutput) show0(regions *AWSRegions) {\n\tfor _, r := range *regions {\n\t\tfmt.Fprintf(lo.w, \"%-25s %20s\\n\", r.Name, r.GetLatencyStr())\n\t}\n}\n\nfunc (lo *LatencyOutput) show1(regions *AWSRegions) {\n\toutFmt := \"%5v %-15s %-30s %20s\\n\"\n\tfmt.Fprintf(lo.w, outFmt, \"\", \"Code\", \"Region\", \"Latency\")\n\tfor i, r := range *regions {\n\t\tfmt.Fprintf(lo.w, outFmt, i, r.Code, r.Name, r.GetLatencyStr())\n\t}\n}\n\nfunc (lo *LatencyOutput) show2(regions *AWSRegions) {\n\t// format\n\toutFmt := \"%5v %-15s %-25s\"\n\toutFmt += strings.Repeat(\" %15s\", lo.Repeats) + \" %15s\\n\"\n\t// header\n\toutStr := []interface{}{\"\", \"Code\", \"Region\"}\n\tfor i := 0; i < lo.Repeats; i++ {\n\t\toutStr = append(outStr, \"Try #\"+strconv.Itoa(i+1))\n\t}\n\toutStr = append(outStr, \"Avg Latency\")\n\n\t// show header\n\tfmt.Fprintf(lo.w, outFmt, outStr...)\n\n\t// each region stats\n\tfor i, r := range *regions {\n\t\toutData := []interface{}{strconv.Itoa(i), r.Code, r.Name}\n\t\tfor n := 0; n < lo.Repeats; n++ {\n\t\t\toutData = append(outData, fmt.Sprintf(\"%.2f ms\",\n\t\t\t\tDuration2ms(r.Latencies[n])))\n\t\t}\n\t\toutData = append(outData, fmt.Sprintf(\"%.2f ms\", r.GetLatency()))\n\t\tfmt.Fprintf(lo.w, outFmt, outData...)\n\t}\n}\n\n// Show print data\nfunc (lo *LatencyOutput) Show(regions *AWSRegions) {\n\tswitch lo.Level {\n\tcase ShowOnlyRegions:\n\t\tlo.show(regions)\n\tcase 0:\n\t\tlo.show0(regions)\n\tcase 1:\n\t\tlo.show1(regions)\n\tcase 2:\n\t\tlo.show2(regions)\n\t}\n}\n\n// GetRegions returns a list of regions\nfunc GetRegions() AWSRegions {\n\treturn AWSRegions{\n\t\tNewRegion(\"Africa (Cape Town)\", \"af-south-1\"),\n\t\tNewRegion(\"Asia Pacific (Hong Kong)\", \"ap-east-1\"),\n\t\tNewRegion(\"Asia Pacific (Tokyo)\", \"ap-northeast-1\"),\n\t\tNewRegion(\"Asia Pacific (Seoul)\", \"ap-northeast-2\"),\n\t\tNewRegion(\"Asia Pacific (Osaka)\", \"ap-northeast-3\"),\n\t\tNewRegion(\"Asia Pacific (Mumbai)\", \"ap-south-1\"),\n\t\tNewRegion(\"Asia Pacific (Hyderabad)\", \"ap-south-2\"),\n\t\tNewRegion(\"Asia Pacific (Singapore)\", \"ap-southeast-1\"),\n\t\tNewRegion(\"Asia Pacific (Sydney)\", \"ap-southeast-2\"),\n\t\tNewRegion(\"Asia Pacific (Jakarta)\", \"ap-southeast-3\"),\n\t\tNewRegion(\"Asia Pacific (Melbourne)\", \"ap-southeast-4\"),\n\t\tNewRegion(\"Canada (Central)\", \"ca-central-1\"),\n\t\tNewRegion(\"Europe (Frankfurt)\", \"eu-central-1\"),\n\t\tNewRegion(\"Europe (Zurich)\", \"eu-central-2\"),\n\t\tNewRegion(\"Europe (Stockholm)\", \"eu-north-1\"),\n\t\tNewRegion(\"Europe (Milan)\", \"eu-south-1\"),\n\t\tNewRegion(\"Europe (Spain)\", \"eu-south-2\"),\n\t\tNewRegion(\"Europe (Ireland)\", \"eu-west-1\"),\n\t\tNewRegion(\"Europe (London)\", \"eu-west-2\"),\n\t\tNewRegion(\"Europe (Paris)\", \"eu-west-3\"),\n\t\tNewRegion(\"Middle East (UAE)\", \"me-central-1\"),\n\t\tNewRegion(\"Middle East (Bahrain)\", \"me-south-1\"),\n\t\tNewRegion(\"South America (Sao Paulo)\", \"sa-east-1\"),\n\t\tNewRegion(\"US East (N. Virginia)\", \"us-east-1\"),\n\t\tNewRegion(\"US East (Ohio)\", \"us-east-2\"),\n\t\tNewRegion(\"US West (N. California)\", \"us-west-1\"),\n\t\tNewRegion(\"US West (Oregon)\", \"us-west-2\"),\n\t\tNewRegion(\"Israel (Tel Aviv)\", \"il-central-1\"),\n\t}\n}\n\n// CalcLatency returns list of aws regions sorted by Latency\nfunc CalcLatency(regions AWSRegions, repeats int, useHTTP bool, useHTTPS bool, service string) {\n\tregions.SetService(service)\n\tswitch {\n\tcase useHTTP:\n\t\tregions.SetCheckType(CheckTypeHTTP)\n\tcase useHTTPS:\n\t\tregions.SetCheckType(CheckTypeHTTPS)\n\tdefault:\n\t\tregions.SetCheckType(CheckTypeTCP)\n\t}\n\tregions.SetDefaultTarget()\n\n\tvar wg sync.WaitGroup\n\tfor n := 1; n <= repeats; n++ {\n\t\twg.Add(len(regions))\n\t\tfor i := range regions {\n\t\t\tgo regions[i].CheckLatency(&wg)\n\t\t}\n\t\twg.Wait()\n\t}\n\n\tsort.Sort(regions)\n}\n"
  },
  {
    "path": "utils_test.go",
    "content": "package awsping\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestDuration(t *testing.T) {\n\tinput := time.Duration(1 * time.Second)\n\twant := 1000.0\n\tgot := Duration2ms(input)\n\tif got != want {\n\t\tt.Errorf(\"Duration was incorrect, got: %f, want: %f.\", got, want)\n\t}\n}\n\nfunc TestRandomString(t *testing.T) {\n\ttests := []struct {\n\t\tn   int\n\t\tres string\n\t}{\n\t\t{1, \"\"},\n\t\t{5, \"\"},\n\t\t{10, \"\"},\n\t}\n\n\tfor idx, test := range tests {\n\t\ttest.res = mkRandomString(test.n)\n\t\tif len(test.res) != test.n {\n\t\t\tt.Errorf(\"Try %d: n=%d, got: %s (len=%d), want: %d.\",\n\t\t\t\tidx, test.n, test.res, len(test.res), test.n)\n\t\t}\n\t}\n}\n\nfunc TestOutputShowOnlyRegions(t *testing.T) {\n\tvar b bytes.Buffer\n\n\tlo := NewOutput(ShowOnlyRegions, 0)\n\tlo.w = &b\n\n\tregions := GetRegions()[:2]\n\tlo.Show(&regions)\n\n\tgot := b.String()\n\twant := \"af-south-1      Africa (Cape Town)\\n\" +\n\t\t\"ap-east-1       Asia Pacific (Hong Kong)\\n\"\n\n\tif got != want {\n\t\tt.Errorf(\"Show:\\ngot =%q\\nwant=%q\", got, want)\n\t}\n}\n\nfunc TestOutputShow0(t *testing.T) {\n\tvar b bytes.Buffer\n\n\tlo := NewOutput(0, 0)\n\tlo.w = &b\n\n\tregions := GetRegions()[:2]\n\tregions[0].Latencies = []time.Duration{15 * time.Millisecond}\n\tregions[1].Latencies = []time.Duration{25 * time.Millisecond}\n\n\tlo.Show(&regions)\n\n\twant := \"Africa (Cape Town)                    15.00 ms\\n\" +\n\t\t\"Asia Pacific (Hong Kong)              25.00 ms\\n\"\n\tgot := b.String()\n\tif got != want {\n\t\tt.Errorf(\"Show0 failed:\\ngot =%q\\nwant=%q\", got, want)\n\t}\n}\n\nfunc TestOutputShow1(t *testing.T) {\n\tvar b bytes.Buffer\n\n\tlo := NewOutput(1, 0)\n\tlo.w = &b\n\n\tregions := GetRegions()[:2]\n\tregions[0].Latencies = []time.Duration{15 * time.Millisecond}\n\tregions[1].Latencies = []time.Duration{25 * time.Millisecond}\n\n\tlo.Show(&regions)\n\n\tgot := b.String()\n\twant := \"      Code            Region                                      Latency\\n\" +\n\t\t\"    0 af-south-1      Africa (Cape Town)                         15.00 ms\\n\" +\n\t\t\"    1 ap-east-1       Asia Pacific (Hong Kong)                   25.00 ms\\n\"\n\tif got != want {\n\t\tt.Errorf(\"Show1 failed:\\ngot =%q\\nwant=%q\", got, want)\n\t}\n}\n\nfunc TestOutputShow2(t *testing.T) {\n\tvar b bytes.Buffer\n\n\tlo := NewOutput(2, 2)\n\tlo.w = &b\n\n\tregions := GetRegions()[:2]\n\tregions[0].Latencies = []time.Duration{15 * time.Millisecond, 17 * time.Millisecond}\n\tregions[1].Latencies = []time.Duration{25 * time.Millisecond, 26 * time.Millisecond}\n\n\tlo.Show(&regions)\n\n\tgot := b.String()\n\twant := \"      Code            Region                             Try #1          Try #2     Avg Latency\\n\" +\n\t\t\"    0 af-south-1      Africa (Cape Town)               15.00 ms        17.00 ms        16.00 ms\\n\" +\n\t\t\"    1 ap-east-1       Asia Pacific (Hong Kong)         25.00 ms        26.00 ms        25.50 ms\\n\"\n\tif got != want {\n\t\tt.Errorf(\"Show2 failed:\\ngot =%q\\nwant=%q\", got, want)\n\t}\n}\n\nfunc TestCalcLatency(t *testing.T) {\n\n\tregions := GetRegions()[:3]\n\tregions[0].Request = &testRequest{duration: 30 * time.Millisecond}\n\tregions[1].Request = &testRequest{duration: 7 * time.Millisecond}\n\tregions[2].Request = &testRequest{duration: 15 * time.Millisecond}\n\n\tregionsStats := make(AWSRegions, regions.Len())\n\n\tcheckSort := func(origIndex, sortedIdx int) {\n\t\tgot := regionsStats[sortedIdx].Name\n\t\twant := regions[origIndex].Name\n\n\t\tif got != want {\n\t\t\tt.Errorf(\"CalcLatency failed:\\ngot=%q\\nwant=%q\\norig=%d\\nsorted=%d\",\n\t\t\t\tgot, want, origIndex, sortedIdx)\n\t\t}\n\t}\n\n\tfor i := 1; i < 4; i++ {\n\t\tcopy(regionsStats, regions)\n\n\t\tswitch i {\n\t\tcase 1:\n\t\t\tCalcLatency(regionsStats, 1, false, false, \"ec2\")\n\t\tcase 2:\n\t\t\tCalcLatency(regionsStats, 1, true, false, \"ec2\")\n\t\tdefault:\n\t\t\tCalcLatency(regionsStats, 1, true, true, \"ec2\")\n\t\t}\n\n\t\tcheckSort(0, 2)\n\t\tcheckSort(1, 0)\n\t\tcheckSort(2, 1)\n\t}\n}\n"
  }
]