├── .editorconfig ├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── fast └── fast-api.go ├── format └── format.go ├── glide.lock ├── glide.yaml ├── main.go └── meters └── bandwidth_meter.go /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset=utf-8 3 | end_of_line=lf 4 | insert_final_newline=true 5 | indent_style=space 6 | indent_size=4 7 | 8 | [{.babelrc,.stylelintrc,jest.config,.eslintrc,*.json,*.jsb3,*.jsb2,*.bowerrc,*.yml,*.yaml}] 9 | indent_style=space 10 | indent_size=2 11 | 12 | [*.go] 13 | indent_style=tab 14 | tab_width=4 15 | 16 | [Makefile] 17 | indent_style=tab 18 | tab_width=4 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/* 2 | vendor/* 3 | dist/* 4 | 5 | *.bak 6 | 7 | fast-cli 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.6.x 4 | - 1.7.x 5 | - 1.8.x 6 | install: make deps 7 | script: make 8 | before_deploy: make dist 9 | deploy: 10 | provider: releases 11 | api_key: 12 | secure: rF8Md1gk6G13cRDeIxZYFj9Zg5uQH8iWGIOK9xlrzogB4pOxcH1pszAm4PO1WNYIOdHWaKPQpSW9XwTMv4ChRBfEXmCHhHL9tLluQS0iM5WT2lHzZMg8VF/FHyP906AZOZYal3IKdPPYeMB/VPo5x4lqXD2vWv4+dMLXqMwXsj5LfVTr4+hDJUm4ffZ0oQTu4cjr6OCJxGjtiDMwxab7Wo0eV8HRMq5jsv+I5BcJxNP0Sfz2MGK32IImrb12CRFDH84wuzTckMAaicF+1s2p0DYBLqeasOcMLVw3lVXPMCV8AlnmET5V42fW0JtXgMiluSvDiwzwtWWWGgx9yhA4mMZHuVyc1kyxwijyXEhtMf2KFvPGBGmjewRPhmOmt8/xq10fdyCpIkqloIRLNvSe+qb0Wbf5VBe1Xbi6vY+vVw8uyk1UP5lDU1mvplaqv4zYtq8R1xeCC+yWiLGptIJl6CrIEa9nW8HCPn2h8TE3eeNAqSx4MRgQp7EdyOz95fEch68hceRZpiVZt428DBeMEZ9hdEX7EakBclxlB8OHYRLT5MUbDNxF9QShRfSMRaqmXNjrtxnoIMjDN7XFxk3ih2moqgSDwV+E0u5hKbdZtQIWpLbEc4gZspnA/b/HLRzcvfxi/8At+9b++mB2T78MYvew2yG18A3QMEjJm2w1MAk= 13 | file_glob: true 14 | file: 15 | - dist/*-v*.tar.gz 16 | - dist/*-v*.zip 17 | skip_cleanup: true 18 | on: 19 | tags: true 20 | condition: $TRAVIS_TAG =~ ^v[0-9]+\.[0-9]+(\.[0-9]+)?(-[^ \t\r\n\v\f]*)?$ && $TRAVIS_GO_VERSION =~ ^1\.8\.[0-9]+$ 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2017 Gus Esquivel 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile 3 | # 4 | # The kickoff point for all project management commands. 5 | # 6 | 7 | GOCC := go 8 | 9 | # Program version 10 | VERSION := $(shell git describe --always --tags) 11 | 12 | # Binary name for bintray 13 | BIN_NAME=fast-cli 14 | 15 | # Project owner for bintray 16 | OWNER=gesquive 17 | 18 | # Project name for bintray 19 | PROJECT_NAME=fast-cli 20 | 21 | # Project url used for builds 22 | # examples: github.com, bitbucket.org 23 | REPO_HOST_URL=github.com 24 | 25 | # Grab the current commit 26 | GIT_COMMIT=$(shell git rev-parse HEAD) 27 | 28 | # Check if there are uncommited changes 29 | GIT_DIRTY=$(shell test -n "`git status --porcelain`" && echo "+CHANGES" || true) 30 | 31 | # Use a local vendor directory for any dependencies; comment this out to 32 | # use the global GOPATH instead 33 | # GOPATH=$(PWD) 34 | 35 | INSTALL_PATH=$(GOPATH)/src/${REPO_HOST_URL}/${OWNER}/${PROJECT_NAME} 36 | 37 | PKG_TAR=cd dist/${PKG} && tar --exclude=".*" --owner=0 --group=0 -zcf ../${PROJECT_NAME}-${VERSION}-${PKG}.tar.gz * 38 | PKG_ZIP=cd dist/${PKG} && zip --exclude .\* -qr ../${PROJECT_NAME}-${VERSION}-${PKG}.zip * 39 | PKG_DST=cd dist && find . -mindepth 1 -maxdepth 1 -type d -exec 40 | 41 | default: test build 42 | 43 | .PHONY: help 44 | help: 45 | @echo 'Management commands for ${PROJECT_NAME}:' 46 | @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | \ 47 | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-30s\033[0m %s\n", $$1, $$2}' 48 | @echo 49 | 50 | .PHONY: build 51 | build: ## Compile the project 52 | @echo "building ${OWNER} ${BIN_NAME} ${VERSION}" 53 | @echo "GOPATH=${GOPATH}" 54 | ${GOCC} build -ldflags "-X main.version=${VERSION} -X main.dirty=${GIT_DIRTY}" -o ${BIN_NAME} 55 | 56 | .PHONY: install 57 | install: build ## Install the binaries on this computer 58 | install -d ${DESTDIR}/usr/local/bin/ 59 | install -m 755 ./${BIN_NAME} ${DESTDIR}/usr/local/bin/${BIN_NAME} 60 | 61 | .PHONY: deps 62 | deps: glide ## Download project dependencies 63 | glide install 64 | 65 | .PHONY: test 66 | test: glide ## Run golang tests 67 | ${GOCC} test $(shell glide novendor) 68 | 69 | .PHONY: bench 70 | bench: glide ## Run golang benchmarks 71 | ${GOCC} test -benchmem -bench=. $(shell glide novendor) 72 | 73 | .PHONY: clean 74 | clean: ## Clean the directory tree of artifacts 75 | ${GOCC} clean 76 | rm -f ./${BIN_NAME}.test 77 | rm -f ./${BIN_NAME} 78 | rm -rf ./dist 79 | 80 | .PHONY: build-all 81 | build-all: gox 82 | gox -verbose \ 83 | -ldflags "-X main.version=${VERSION} -X main.dirty=${GIT_DIRTY}" \ 84 | -os="linux darwin windows" \ 85 | -arch="amd64 386" \ 86 | -output="dist/{{.OS}}-{{.Arch}}/{{.Dir}}" . 87 | 88 | .PHONY: dist 89 | dist: build-all ## Cross compile the full distribution 90 | $(PKG_DST) cp ../README.md "{}" \; 91 | $(PKG_DST) cp ../LICENSE "{}" \; 92 | $(eval PKG=darwin-386) $(PKG_TAR) 93 | $(eval PKG=darwin-amd64) $(PKG_TAR) 94 | $(eval PKG=linux-386) $(PKG_TAR) 95 | $(eval PKG=linux-amd64) $(PKG_TAR) 96 | $(eval PKG=windows-386) $(PKG_ZIP) 97 | $(eval PKG=windows-amd64) $(PKG_ZIP) 98 | $(PKG_DST) rm -rf "{}" \; 99 | 100 | .PHONY: fmt 101 | fmt: ## Reformat the source tree with gofmt 102 | find . -name '*.go' -not -path './.vendor/*' -exec gofmt -w=true {} ';' 103 | 104 | .PHONY: link 105 | link: $(INSTALL_PATH) ## Symlink this project into the GOPATH 106 | $(INSTALL_PATH): 107 | @mkdir -p `dirname $(INSTALL_PATH)` 108 | @ln -s $(PWD) $(INSTALL_PATH) >/dev/null 2>&1 109 | 110 | .PHONY: glide 111 | glide: 112 | @command -v glide >/dev/null 2>&1 || \ 113 | echo "Installing glide" && ${GOCC} get -u github.com/Masterminds/glide 114 | 115 | .PHONY: gox 116 | gox: 117 | @command -v gox >/dev/null 2>&1 || \ 118 | echo "Installing gox" && ${GOCC} get -u github.com/mitchellh/gox 119 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fast-cli 2 | [![Travis CI](https://img.shields.io/travis/gesquive/fast-cli/master.svg?style=flat-square)](https://travis-ci.org/gesquive/fast-cli) 3 | [![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/gesquive/fast-cli/blob/master/LICENSE) 4 | [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/gesquive/fast-cli) 5 | 6 | fast-cli estimates your current internet download speed by performing a series of downloads from Netflix's fast.com servers. 7 | 8 | 9 | ## Installing 10 | 11 | ### Compile 12 | This project requires go 1.6+ to compile. Just run `go get -u github.com/gesquive/fast-cli` and the executable should be built for you automatically in your `$GOPATH`. 13 | 14 | Optionally you can run `make install` to build and copy the executable to `/usr/local/bin/` with correct permissions. 15 | 16 | ### Download 17 | Alternately, you can download the latest release for your platform from [github](https://github.com/gesquive/fast-cli/releases). 18 | 19 | Once you have an executable, make sure to copy it somewhere on your path like `/usr/local/bin` or `C:/Program Files/`. 20 | If on a \*nix/mac system, make sure to run `chmod +x /path/to/fast-cli`. 21 | 22 | ## Usage 23 | 24 | ```console 25 | fast-cli estimates your current internet download speed by performing a series of downloads from Netflix's fast.com servers. 26 | 27 | Usage: 28 | fast-cli [flags] 29 | 30 | Flags: 31 | -h, --help help for fast-cli 32 | -n, --no-https Do not use HTTPS when connecting 33 | -s, --simple Only display the result, no dynamic progress bar 34 | --version Display the version number and exit 35 | ``` 36 | Optionally, a hidden debug flag is available in case you need additional output. 37 | ```console 38 | Hidden Flags: 39 | -D, --debug Include debug statements in log output 40 | ``` 41 | 42 | ## Documentation 43 | 44 | This documentation can be found at github.com/gesquive/fast-cli 45 | 46 | ## License 47 | 48 | This package is made available under an MIT-style license. See LICENSE. 49 | 50 | ## Contributing 51 | 52 | PRs are always welcome! 53 | -------------------------------------------------------------------------------- /fast/fast-api.go: -------------------------------------------------------------------------------- 1 | package fast 2 | 3 | import "fmt" 4 | import "bytes" 5 | import "net/http" 6 | import "io" 7 | import "regexp" 8 | import "github.com/gesquive/cli" 9 | 10 | // UseHTTPS sets if HTTPS is used 11 | var UseHTTPS = true 12 | 13 | // GetDlUrls returns a list of urls to the fast api downloads 14 | func GetDlUrls(urlCount uint64) (urls []string) { 15 | token := getFastToken() 16 | 17 | httpProtocol := "https" 18 | if !UseHTTPS { 19 | httpProtocol = "http" 20 | } 21 | 22 | url := fmt.Sprintf("%s://api.fast.com/netflix/speedtest?https=%t&token=%s&urlCount=%d", 23 | httpProtocol, UseHTTPS, token, urlCount) 24 | // fmt.Printf("url=%s\n", url) 25 | cli.Debug("getting url list from %s", url) 26 | 27 | jsonData, _ := getPage(url) 28 | 29 | re := regexp.MustCompile("(?U)\"url\":\"(.*)\"") 30 | reUrls := re.FindAllStringSubmatch(jsonData, -1) 31 | 32 | cli.Debug("urls:") 33 | for _, arr := range reUrls { 34 | urls = append(urls, arr[1]) 35 | cli.Debug(" - %s", arr[1]) 36 | } 37 | 38 | return 39 | } 40 | 41 | // GetDefaultURL returns the fallback download URL 42 | func GetDefaultURL() (url string) { 43 | httpProtocol := "https" 44 | if !UseHTTPS { 45 | httpProtocol = "http" 46 | } 47 | url = fmt.Sprintf("%s://api.fast.com/netflix/speedtest", httpProtocol) 48 | return 49 | } 50 | 51 | func getFastToken() (token string) { 52 | baseURL := "https://fast.com" 53 | if !UseHTTPS { 54 | baseURL = "http://fast.com" 55 | } 56 | fastBody, _ := getPage(baseURL) 57 | 58 | // Extract the app script url 59 | re := regexp.MustCompile("app-.*\\.js") 60 | scriptNames := re.FindAllString(fastBody, 1) 61 | 62 | scriptURL := fmt.Sprintf("%s/%s", baseURL, scriptNames[0]) 63 | cli.Debug("trying to get fast api token from %s", scriptURL) 64 | 65 | // Extract the token 66 | scriptBody, _ := getPage(scriptURL) 67 | 68 | re = regexp.MustCompile("token:\"[[:alpha:]]*\"") 69 | tokens := re.FindAllString(scriptBody, 1) 70 | 71 | if len(tokens) > 0 { 72 | token = tokens[0][7 : len(tokens[0])-1] 73 | cli.Debug("token found: %s", token) 74 | } else { 75 | cli.Warn("no token found") 76 | } 77 | 78 | return 79 | } 80 | 81 | func getPage(url string) (contents string, err error) { 82 | // Create the string buffer 83 | buffer := bytes.NewBuffer(nil) 84 | 85 | // Get the data 86 | resp, err := http.Get(url) 87 | if err != nil { 88 | return contents, err 89 | } 90 | defer resp.Body.Close() 91 | 92 | // Writer the body to file 93 | _, err = io.Copy(buffer, resp.Body) 94 | if err != nil { 95 | return contents, err 96 | } 97 | contents = buffer.String() 98 | 99 | return 100 | } 101 | -------------------------------------------------------------------------------- /format/format.go: -------------------------------------------------------------------------------- 1 | package format 2 | 3 | import "fmt" 4 | import "github.com/dustin/go-humanize" 5 | 6 | // BitsPerSec formats a byte count 7 | func BitsPerSec(bytes float64) string { 8 | prettySize, prettyUnit := humanize.ComputeSI(bytes * 8) 9 | return fmt.Sprintf("%7.2f %sbps", prettySize, prettyUnit) 10 | } 11 | 12 | // Bytes formats a byte count 13 | func Bytes(bytes uint64) string { 14 | prettySize, prettyUnit := humanize.ComputeSI(float64(bytes)) 15 | return fmt.Sprintf("%3.f %sB", prettySize, prettyUnit) 16 | } 17 | 18 | // Percent formats a percent 19 | func Percent(current uint64, total uint64) string { 20 | return fmt.Sprintf("%5.1f%%", float64(current)/float64(total)*100) 21 | } 22 | -------------------------------------------------------------------------------- /glide.lock: -------------------------------------------------------------------------------- 1 | hash: caec6f1e535114657678cee8360dd904ea60897437b3c40a1c41bd0a30763468 2 | updated: 2017-05-31T16:49:11.298536241-05:00 3 | imports: 4 | - name: github.com/dustin/go-humanize 5 | version: 259d2a102b871d17f30e3cd9881a642961a1e486 6 | - name: github.com/fatih/color 7 | version: 9131ab34cf20d2f6d83fdc67168a5430d1c7dc23 8 | - name: github.com/gesquive/cli 9 | version: 944950c5782fb9490e1e8c2b46d21194aae2c21b 10 | - name: github.com/inconshreveable/mousetrap 11 | version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 12 | - name: github.com/mattn/go-colorable 13 | version: 5411d3eea5978e6cdc258b30de592b60df6aba96 14 | repo: https://github.com/mattn/go-colorable 15 | - name: github.com/mattn/go-isatty 16 | version: 57fdcb988a5c543893cc61bce354a6e24ab70022 17 | repo: https://github.com/mattn/go-isatty 18 | - name: github.com/spf13/cobra 19 | version: 8d4ce3549a0bf0e3569df3aae7423b7743cd05a9 20 | - name: github.com/spf13/pflag 21 | version: e57e3eeb33f795204c1ca35f56c44f83227c6e66 22 | - name: golang.org/x/sys 23 | version: e24f485414aeafb646f6fca458b0bf869c0880a1 24 | repo: https://go.googlesource.com/sys 25 | subpackages: 26 | - unix 27 | testImports: [] 28 | -------------------------------------------------------------------------------- /glide.yaml: -------------------------------------------------------------------------------- 1 | package: github.com/gesquive/fast-cli 2 | import: 3 | - package: github.com/spf13/cobra 4 | - package: github.com/inconshreveable/mousetrap 5 | - package: github.com/dustin/go-humanize 6 | - package: github.com/gesquive/cli 7 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "os" 4 | import "fmt" 5 | import "io" 6 | import "net/http" 7 | import "strconv" 8 | import "time" 9 | 10 | import "github.com/spf13/cobra" 11 | import "github.com/gesquive/cli" 12 | import "github.com/gesquive/fast-cli/fast" 13 | import "github.com/gesquive/fast-cli/format" 14 | import "github.com/gesquive/fast-cli/meters" 15 | 16 | var version = "v0.2.10" 17 | var dirty = "" 18 | var displayVersion string 19 | 20 | var cfgFile string 21 | var logDebug bool 22 | var notHTTPS bool 23 | var simpleProgress bool 24 | var showVersion bool 25 | 26 | //RootCmd is the only command 27 | var RootCmd = &cobra.Command{ 28 | Use: "fast-cli", 29 | Short: "Estimates your current internet download speed", 30 | Long: `fast-cli estimates your current internet download speed by performing a series of downloads from Netflix's fast.com servers.`, 31 | Run: run, 32 | } 33 | 34 | func main() { 35 | displayVersion = fmt.Sprintf("fast-cli %s%s", 36 | version, 37 | dirty) 38 | Execute(displayVersion) 39 | } 40 | 41 | // Execute adds all child commands to the root command sets flags appropriately. 42 | // This is called by main.main(). It only needs to happen once to the rootCmd. 43 | func Execute(version string) { 44 | displayVersion = version 45 | RootCmd.SetHelpTemplate(fmt.Sprintf("%s\nVersion:\n github.com/gesquive/%s\n", 46 | RootCmd.HelpTemplate(), displayVersion)) 47 | if err := RootCmd.Execute(); err != nil { 48 | fmt.Println(err) 49 | os.Exit(-1) 50 | } 51 | } 52 | 53 | func init() { 54 | cobra.OnInitialize(initLog) 55 | 56 | RootCmd.PersistentFlags().BoolVarP(¬HTTPS, "no-https", "n", false, "Do not use HTTPS when connecting") 57 | RootCmd.PersistentFlags().BoolVarP(&simpleProgress, "simple", "s", false, "Only display the result, no dynamic progress bar") 58 | RootCmd.PersistentFlags().BoolVar(&showVersion, "version", false, "Display the version number and exit") 59 | RootCmd.PersistentFlags().BoolVarP(&logDebug, "debug", "D", false, "Write debug messages to console") 60 | 61 | RootCmd.PersistentFlags().MarkHidden("debug") 62 | //TODO: Allow to estimate using time or size 63 | } 64 | 65 | func initLog() { 66 | cli.SetPrintLevel(cli.LevelInfo) 67 | if logDebug { 68 | cli.SetPrintLevel(cli.LevelDebug) 69 | } 70 | if notHTTPS { 71 | cli.Debugln("Not using HTTPS") 72 | } else { 73 | cli.Debugln("Using HTTPS") 74 | } 75 | } 76 | 77 | func run(cmd *cobra.Command, args []string) { 78 | if showVersion { 79 | cli.Infoln(displayVersion) 80 | os.Exit(0) 81 | } 82 | count := uint64(3) 83 | fast.UseHTTPS = !notHTTPS 84 | urls := fast.GetDlUrls(count) 85 | cli.Debugf("Got %d from fast service\n", len(urls)) 86 | 87 | if len(urls) == 0 { 88 | cli.Warnf("Using fallback endpoint\n") 89 | urls = append(urls, fast.GetDefaultURL()) 90 | } 91 | 92 | err := calculateBandwidth(urls) 93 | if err != nil { 94 | fmt.Fprintf(os.Stderr, "%v\n", err) 95 | } 96 | } 97 | 98 | func calculateBandwidth(urls []string) (err error) { 99 | client := &http.Client{} 100 | count := uint64(len(urls)) 101 | 102 | primaryBandwidthReader := meters.BandwidthMeter{} 103 | bandwidthMeter := meters.BandwidthMeter{} 104 | ch := make(chan *copyResults, 1) 105 | bytesToRead := uint64(0) 106 | completed := uint64(0) 107 | 108 | for i := uint64(0); i < count; i++ { 109 | // Create the HTTP request 110 | request, err := http.NewRequest("GET", urls[i], nil) 111 | if err != nil { 112 | return err 113 | } 114 | request.Header.Set("User-Agent", displayVersion) 115 | 116 | // Get the HTTP Response 117 | response, err := client.Do(request) 118 | if err != nil { 119 | return err 120 | } 121 | defer response.Body.Close() 122 | 123 | // Set information for the leading index 124 | if i == 0 { 125 | // Try to get content length 126 | contentLength := response.Header.Get("Content-Length") 127 | calculatedLength, err := strconv.Atoi(contentLength) 128 | if err != nil { 129 | calculatedLength = 26214400 130 | } 131 | bytesToRead = uint64(calculatedLength) 132 | cli.Debugf("Download Size=%d\n", bytesToRead) 133 | 134 | tapMeter := io.TeeReader(response.Body, &primaryBandwidthReader) 135 | go asyncCopy(i, ch, &bandwidthMeter, tapMeter) 136 | } else { 137 | // Start reading 138 | go asyncCopy(i, ch, &bandwidthMeter, response.Body) 139 | } 140 | 141 | } 142 | 143 | if !simpleProgress { 144 | cli.Infof("Estimating current download speed\n") 145 | } 146 | for { 147 | select { 148 | case results := <-ch: 149 | if results.err != nil { 150 | fmt.Fprintf(os.Stdout, "\n%v\n", results.err) 151 | os.Exit(1) 152 | } 153 | 154 | completed++ 155 | if !simpleProgress { 156 | fmt.Printf("\r%s - %s", 157 | format.BitsPerSec(bandwidthMeter.Bandwidth()), 158 | format.Percent(primaryBandwidthReader.BytesRead(), bytesToRead)) 159 | fmt.Printf(" \n") 160 | fmt.Printf("Completed in %.1f seconds\n", bandwidthMeter.Duration().Seconds()) 161 | } else { 162 | fmt.Printf("%s\n", format.BitsPerSec(bandwidthMeter.Bandwidth())) 163 | } 164 | return nil 165 | case <-time.After(100 * time.Millisecond): 166 | if !simpleProgress { 167 | fmt.Printf("\r%s - %s", 168 | format.BitsPerSec(bandwidthMeter.Bandwidth()), 169 | format.Percent(primaryBandwidthReader.BytesRead(), bytesToRead)) 170 | } 171 | } 172 | } 173 | } 174 | 175 | type copyResults struct { 176 | index uint64 177 | bytesWritten uint64 178 | err error 179 | } 180 | 181 | func asyncCopy(index uint64, channel chan *copyResults, writer io.Writer, reader io.Reader) { 182 | bytesWritten, err := io.Copy(writer, reader) 183 | channel <- ©Results{index, uint64(bytesWritten), err} 184 | } 185 | 186 | func sumArr(array []uint64) (sum uint64) { 187 | for i := 0; i < len(array); i++ { 188 | sum = sum + array[i] 189 | } 190 | return 191 | } 192 | -------------------------------------------------------------------------------- /meters/bandwidth_meter.go: -------------------------------------------------------------------------------- 1 | package meters 2 | 3 | import "time" 4 | 5 | // BandwidthMeter counts the number of bytes written to it over time. 6 | type BandwidthMeter struct { 7 | bytesRead uint64 8 | start time.Time 9 | lastRead time.Time 10 | } 11 | 12 | // Write implements the io.Writer interface. 13 | func (br *BandwidthMeter) Write(p []byte) (int, error) { 14 | // Always completes and never returns an error. 15 | br.lastRead = time.Now().UTC() 16 | n := len(p) 17 | br.bytesRead += uint64(n) 18 | if br.start.IsZero() { 19 | br.start = br.lastRead 20 | } 21 | 22 | return n, nil 23 | } 24 | 25 | // Start records the start time 26 | func (br *BandwidthMeter) Start() { 27 | br.start = time.Now().UTC() 28 | } 29 | 30 | // Bandwidth returns the current bandwidth 31 | func (br *BandwidthMeter) Bandwidth() (bytesPerSec float64) { 32 | deltaSecs := br.lastRead.Sub(br.start).Seconds() 33 | bytesPerSec = float64(br.bytesRead) / deltaSecs 34 | return 35 | } 36 | 37 | // BytesRead returns the number of bytes read by this BandwidthMeter 38 | func (br *BandwidthMeter) BytesRead() (bytes uint64) { 39 | bytes = br.bytesRead 40 | return 41 | } 42 | 43 | // Duration returns the current duration 44 | func (br *BandwidthMeter) Duration() (duration time.Duration) { 45 | duration = br.lastRead.Sub(br.start) 46 | return 47 | } 48 | --------------------------------------------------------------------------------