mirror of
https://github.com/librespeed/speedtest-cli.git
synced 2025-06-30 12:59:54 +00:00
Compare commits
27 commits
Author | SHA1 | Date | |
---|---|---|---|
|
14717ac78b | ||
|
95f2c8207a | ||
|
7ed6fe234c | ||
|
0c565b724b | ||
|
c9decb3fda | ||
|
78c9095ca9 | ||
|
63c75be0fc | ||
|
7573b65ebc | ||
|
d33e431b58 | ||
|
d23c1b7b72 | ||
|
058cd387c1 | ||
|
b1daf1c451 | ||
|
67adaa2956 | ||
|
e5c131fe62 | ||
|
11183cbf98 | ||
|
6103965f44 | ||
|
6059f16e57 | ||
|
24b7826d78 | ||
|
092760f344 | ||
|
0f908e806c | ||
|
af2908a51d | ||
|
c996e515b1 | ||
|
954e973203 | ||
|
c2af01baf5 | ||
|
df77b3ee21 | ||
|
8e95ecefd7 | ||
|
9a8bca8fa0 |
15 changed files with 453 additions and 236 deletions
128
.goreleaser.yml
128
.goreleaser.yml
|
@ -1,74 +1,84 @@
|
|||
project_name: 'librespeed-cli'
|
||||
version: 2
|
||||
project_name: "librespeed-cli"
|
||||
#dist: ./out
|
||||
before:
|
||||
hooks:
|
||||
- go mod download
|
||||
builds:
|
||||
- main: ./main.go
|
||||
id: upx
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags:
|
||||
- -w -s -X "librespeed-cli/defs.ProgName={{ .ProjectName }}" -X "librespeed-cli/defs.ProgVersion=v{{ .Version }}" -X "librespeed-cli/defs.BuildDate={{ .Date }}"
|
||||
goos:
|
||||
- linux
|
||||
- darwin
|
||||
- freebsd
|
||||
goarch:
|
||||
- 386
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
- mips
|
||||
- mipsle
|
||||
- mips64
|
||||
- mips64le
|
||||
goarm:
|
||||
- 5
|
||||
- 6
|
||||
- 7
|
||||
gomips:
|
||||
- hardfloat
|
||||
- softfloat
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: 386
|
||||
- goos: darwin
|
||||
goarch: arm64
|
||||
hooks:
|
||||
post: ./upx.sh -9 "{{ .Path }}"
|
||||
- main: ./main.go
|
||||
id: no-upx
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags:
|
||||
- -w -s -X "librespeed-cli/defs.ProgName={{ .ProjectName }}" -X "librespeed-cli/defs.ProgVersion=v{{ .Version }}" -X "librespeed-cli/defs.BuildDate={{ .Date }}"
|
||||
goos:
|
||||
- windows
|
||||
- darwin
|
||||
goarch:
|
||||
- 386
|
||||
- amd64
|
||||
- arm64
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: 386
|
||||
- goos: darwin
|
||||
goarch: amd64
|
||||
- main: ./main.go
|
||||
id: upx
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags:
|
||||
- -w -s -X "librespeed-cli/defs.ProgName={{ .ProjectName }}" -X "librespeed-cli/defs.ProgVersion=v{{ .Version }}" -X "librespeed-cli/defs.BuildDate={{ .Date }}"
|
||||
goos:
|
||||
- linux
|
||||
- darwin
|
||||
- freebsd
|
||||
goarch:
|
||||
- "386"
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
goarm:
|
||||
- "5"
|
||||
- "6"
|
||||
- "7"
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: "386"
|
||||
- goos: darwin
|
||||
goarch: arm64
|
||||
hooks:
|
||||
post:
|
||||
- ./upx.sh -9 "{{ .Path }}"
|
||||
- main: ./main.go
|
||||
id: no-upx
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags:
|
||||
- -w -s -X "librespeed-cli/defs.ProgName={{ .ProjectName }}" -X "librespeed-cli/defs.ProgVersion=v{{ .Version }}" -X "librespeed-cli/defs.BuildDate={{ .Date }}"
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
goarch:
|
||||
- "386"
|
||||
- amd64
|
||||
- arm64
|
||||
- mips
|
||||
- mipsle
|
||||
- mips64
|
||||
- mips64le
|
||||
- riscv64
|
||||
gomips:
|
||||
- hardfloat
|
||||
- softfloat
|
||||
ignore:
|
||||
- goos: linux
|
||||
goarch: "386"
|
||||
- goos: linux
|
||||
goarch: amd64
|
||||
- goos: linux
|
||||
goarch: arm64
|
||||
- goos: darwin
|
||||
goarch: "386"
|
||||
- goos: darwin
|
||||
goarch: amd64
|
||||
archives:
|
||||
- format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
formats: ['zip']
|
||||
files:
|
||||
- LICENSE
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
name_template: "checksums.txt"
|
||||
changelog:
|
||||
skip: false
|
||||
disable: false
|
||||
sort: asc
|
||||
release:
|
||||
github:
|
||||
|
|
66
README.md
66
README.md
|
@ -1,7 +1,7 @@
|
|||

|
||||
|
||||
# LibreSpeed command line tool
|
||||
Don't have a GUI but wants to use LibreSpeed servers to test your Internet speed? 🚀
|
||||
Don't have a GUI but want to use LibreSpeed servers to test your Internet speed? 🚀
|
||||
|
||||
`librespeed-cli` comes to rescue!
|
||||
|
||||
|
@ -22,7 +22,7 @@ This is a command line interface for LibreSpeed speed test backends, written in
|
|||
[](https://asciinema.org/a/J17bUAilWI3qR12JyhfGvPwu2)
|
||||
|
||||
## Requirements for compiling
|
||||
- Go 1.14+
|
||||
- Go 1.18+
|
||||
|
||||
## Runtime requirements
|
||||
- Any [Go supported platforms](https://github.com/golang/go/wiki/MinimumRequirements)
|
||||
|
@ -97,11 +97,45 @@ $ makepkg -si
|
|||
|
||||
See the [librespeed-cli Homebrew tap](https://github.com/librespeed/homebrew-tap#setup).
|
||||
|
||||
## Install on Windows
|
||||
|
||||
If you have either [Scoop](https://scoop.sh/) or [Chocolatey](https://chocolatey.org/) installed you can use one of the following commands:
|
||||
|
||||
- Scoop (ensure you have the `extras` bucket added):
|
||||
```
|
||||
> scoop install librespeed-cli
|
||||
```
|
||||
|
||||
- Chocolatey:
|
||||
```
|
||||
> choco install librespeed-cli
|
||||
```
|
||||
|
||||
## Container Image
|
||||
|
||||
You can run `librespeed-cli` in a container.
|
||||
|
||||
1. Build the container image:
|
||||
|
||||
```shell script
|
||||
docker build -t librespeed-cli:latest .
|
||||
```
|
||||
|
||||
2. Run the container:
|
||||
|
||||
```shell script
|
||||
docker run --rm --name librespeed-cli librespeed-cli:latest
|
||||
# With options
|
||||
docker run --rm --name librespeed-cli librespeed-cli:latest --telemetry-level disabled --no-upload
|
||||
# To avoid "Failed to ping target host: socket: permission denied" errors when using --verbose
|
||||
docker run --rm --name librespeed-cli --sysctl net.ipv4.ping_group_range="0 2147483647" librespeed-cli:latest --verbose
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
You can see the full list of supported options with `librespeed-cli -h`:
|
||||
|
||||
```shell script
|
||||
```
|
||||
$ librespeed-cli -h
|
||||
NAME:
|
||||
librespeed-cli - Test your Internet speed with LibreSpeed 🚀
|
||||
|
@ -116,6 +150,8 @@ GLOBAL OPTIONS:
|
|||
--ipv6, -6 Force IPv6 only (default: false)
|
||||
--no-download Do not perform download test (default: false)
|
||||
--no-upload Do not perform upload test (default: false)
|
||||
--no-icmp Do not use ICMP ping. ICMP doesn't work well under Linux
|
||||
at this moment, so you might want to disable it (default: false)
|
||||
--concurrent value Concurrent HTTP requests being made (default: 3)
|
||||
--bytes Display values in bytes instead of bits. Does not affect
|
||||
the image generated by --share, nor output from
|
||||
|
@ -126,27 +162,37 @@ GLOBAL OPTIONS:
|
|||
--share Generate and provide a URL to the LibreSpeed.org share results
|
||||
image, not displayed with --csv (default: false)
|
||||
--simple Suppress verbose output, only show basic information
|
||||
(default: false)
|
||||
(default: false)
|
||||
--csv Suppress verbose output, only show basic information in CSV
|
||||
format. Speeds listed in bit/s and not affected by --bytes
|
||||
(default: false)
|
||||
(default: false)
|
||||
--csv-delimiter CSV_DELIMITER Single character delimiter (CSV_DELIMITER) to use in
|
||||
CSV output. (default: ",")
|
||||
--csv-header Print CSV headers (default: false)
|
||||
--json Suppress verbose output, only show basic information
|
||||
in JSON format. Speeds listed in bit/s and not
|
||||
affected by --bytes (default: false)
|
||||
affected by --bytes (default: false)
|
||||
--list Display a list of LibreSpeed.org servers (default: false)
|
||||
--server SERVER Specify a SERVER ID to test against. Can be supplied
|
||||
multiple times. Cannot be used with --exclude
|
||||
--exclude EXCLUDE EXCLUDE a server from selection. Can be supplied
|
||||
multiple times. Cannot be used with --server
|
||||
--server-json value Use an alternative server list from remote JSON file
|
||||
--local-json value Use an alternative server list from local JSON file
|
||||
--source SOURCE SOURCE IP address to bind to
|
||||
--local-json value Use an alternative server list from local JSON file,
|
||||
or read from stdin with "--local-json -".
|
||||
--source SOURCE SOURCE IP address to bind to. Incompatible with --interface.
|
||||
--interface INTERFACE The name of the network interface to bind to. Example: "enp0s3".
|
||||
Not supported on Windows and incompatible with --source.
|
||||
Implies --no-icmp.
|
||||
--timeout TIMEOUT HTTP TIMEOUT in seconds. (default: 15)
|
||||
--duration value Upload and download test duration in seconds (default: 15)
|
||||
--chunks value Chunks to download from server, chunk size depends on server configuration (default: 100)
|
||||
--upload-size value Size of payload being uploaded in KiB (default: 1024)
|
||||
--secure Use HTTPS instead of HTTP when communicating with
|
||||
LibreSpeed.org operated servers (default: false)
|
||||
--ca-cert value Use the specified CA certificate PEM bundle file instead
|
||||
of the system certificate trust store
|
||||
--skip-cert-verify Skip verifying SSL certificate for HTTPS connections (self-signed certs) (default: false)
|
||||
--no-pre-allocate Do not pre allocate upload data. Pre allocation is
|
||||
enabled by default to improve upload performance. To
|
||||
support systems with insufficient memory, use this
|
||||
|
@ -190,6 +236,10 @@ locally via `--local-json`). The format is as below:
|
|||
]
|
||||
```
|
||||
|
||||
The `--local-json` option can also read from `stdin`:
|
||||
|
||||
`echo '[{"id": 1,"name": "a","server": "https://speedtest.example.com/","dlURL": "garbage.php","ulURL": "empty.php","pingURL": "empty.php","getIpURL": "getIP.php"}]' | librespeed-cli --local-json - `
|
||||
|
||||
As you can see in the example, all servers have their schemes defined. In case of undefined scheme (e.g. `//example.com`),
|
||||
`librespeed-cli` will use `http` by default, or `https` when the `--secure` option is enabled.
|
||||
|
||||
|
|
3
build.sh
3
build.sh
|
@ -10,9 +10,10 @@ CURRENT_DIR=$(pwd)
|
|||
OUT_DIR=${CURRENT_DIR}/out
|
||||
|
||||
PROGNAME="librespeed-cli"
|
||||
DEFS_PATH="github.com/librespeed/speedtest-cli"
|
||||
BINARY=${PROGNAME}-$(go env GOOS)-$(go env GOARCH)
|
||||
BUILD_DATE=$(date -u "+%Y-%m-%d %H:%M:%S %Z")
|
||||
LDFLAGS="-w -s -X \"librespeed-cli/defs.ProgName=${PROGNAME}\" -X \"librespeed-cli/defs.ProgVersion=${PROGVER}\" -X \"librespeed-cli/defs.BuildDate=${BUILD_DATE}\""
|
||||
LDFLAGS="-w -s -X \"${DEFS_PATH}/defs.ProgName=${PROGNAME}\" -X \"${DEFS_PATH}/defs.ProgVersion=${PROGVER}\" -X \"${DEFS_PATH}/defs.BuildDate=${BUILD_DATE}\""
|
||||
|
||||
if [[ -n "${GOARM}" ]] && [[ "${GOARM}" -gt 0 ]]; then
|
||||
BINARY=${BINARY}v${GOARM}
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
type BytesCounter struct {
|
||||
start time.Time
|
||||
pos int
|
||||
total int
|
||||
total uint64
|
||||
payload []byte
|
||||
reader io.ReadSeeker
|
||||
mebi bool
|
||||
|
@ -33,7 +33,7 @@ func NewCounter() *BytesCounter {
|
|||
func (c *BytesCounter) Write(p []byte) (int, error) {
|
||||
n := len(p)
|
||||
c.lock.Lock()
|
||||
c.total += n
|
||||
c.total += uint64(n)
|
||||
c.lock.Unlock()
|
||||
|
||||
return n, nil
|
||||
|
@ -43,7 +43,7 @@ func (c *BytesCounter) Write(p []byte) (int, error) {
|
|||
func (c *BytesCounter) Read(p []byte) (int, error) {
|
||||
n, err := c.reader.Read(p)
|
||||
c.lock.Lock()
|
||||
c.total += n
|
||||
c.total += uint64(n)
|
||||
c.pos += n
|
||||
if c.pos == c.uploadSize {
|
||||
c.resetReader()
|
||||
|
@ -116,7 +116,7 @@ func (c *BytesCounter) Start() {
|
|||
}
|
||||
|
||||
// Total returns the total bytes read/written
|
||||
func (c *BytesCounter) Total() int {
|
||||
func (c *BytesCounter) Total() uint64 {
|
||||
return c.total
|
||||
}
|
||||
|
||||
|
|
|
@ -24,11 +24,13 @@ const (
|
|||
OptionExclude = "exclude"
|
||||
OptionServerJSON = "server-json"
|
||||
OptionSource = "source"
|
||||
OptionInterface = "interface"
|
||||
OptionTimeout = "timeout"
|
||||
OptionChunks = "chunks"
|
||||
OptionUploadSize = "upload-size"
|
||||
OptionDuration = "duration"
|
||||
OptionSecure = "secure"
|
||||
OptionCACert = "ca-cert"
|
||||
OptionSkipCertVerify = "skip-cert-verify"
|
||||
OptionNoPreAllocate = "no-pre-allocate"
|
||||
OptionVersion = "version"
|
||||
|
@ -40,4 +42,5 @@ const (
|
|||
OptionTelemetryPath = "telemetry-path"
|
||||
OptionTelemetryShare = "telemetry-share"
|
||||
OptionTelemetryExtra = "telemetry-extra"
|
||||
OptionFwmark = "fwmark"
|
||||
)
|
||||
|
|
|
@ -60,8 +60,11 @@ func (s *Server) IsUp() bool {
|
|||
}
|
||||
defer resp.Body.Close()
|
||||
b, _ := ioutil.ReadAll(resp.Body)
|
||||
if len(b) > 0 {
|
||||
log.Debugf("Failed when parsing get IP result: %s", b)
|
||||
}
|
||||
// only return online if the ping URL returns nothing and 200
|
||||
return len(b) == 0 && resp.StatusCode == http.StatusOK
|
||||
return resp.StatusCode == http.StatusOK
|
||||
}
|
||||
|
||||
// ICMPPingAndJitter pings the server via ICMP echos and calculate the average ping and jitter
|
||||
|
@ -185,7 +188,7 @@ func (s *Server) PingAndJitter(count int) (float64, float64, error) {
|
|||
}
|
||||
|
||||
// Download performs the actual download test
|
||||
func (s *Server) Download(silent bool, useBytes, useMebi bool, requests int, chunks int, duration time.Duration) (float64, int, error) {
|
||||
func (s *Server) Download(silent bool, useBytes, useMebi bool, requests int, chunks int, duration time.Duration) (float64, uint64, error) {
|
||||
t := time.Now()
|
||||
defer func() {
|
||||
s.TLog.Logf("Download took %s", time.Now().Sub(t).String())
|
||||
|
@ -277,7 +280,7 @@ Loop:
|
|||
}
|
||||
|
||||
// Upload performs the actual upload test
|
||||
func (s *Server) Upload(noPrealloc, silent, useBytes, useMebi bool, requests int, uploadSize int, duration time.Duration) (float64, int, error) {
|
||||
func (s *Server) Upload(noPrealloc, silent, useBytes, useMebi bool, requests int, uploadSize int, duration time.Duration) (float64, uint64, error) {
|
||||
t := time.Now()
|
||||
defer func() {
|
||||
s.TLog.Logf("Upload took %s", time.Now().Sub(t).String())
|
||||
|
@ -412,6 +415,7 @@ func (s *Server) GetIPInfo(distanceUnit string) (*GetIPResult, error) {
|
|||
if err := json.Unmarshal(b, &ipInfo); err != nil {
|
||||
log.Debugf("Failed when parsing get IP result: %s", err)
|
||||
log.Debugf("Received payload: %s", b)
|
||||
ipInfo.ProcessedString = string(b[:])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
19
dockerfile
Normal file
19
dockerfile
Normal file
|
@ -0,0 +1,19 @@
|
|||
FROM golang:1.20.3-alpine as builder
|
||||
|
||||
RUN apk add --no-cache bash upx
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /usr/src/librespeed-cli
|
||||
|
||||
# Copy librespeed-cli
|
||||
COPY . .
|
||||
|
||||
# Build librespeed-cli
|
||||
RUN ./build.sh
|
||||
|
||||
FROM alpine:3.17
|
||||
|
||||
# Copy librespeed-cli binary
|
||||
COPY --from=builder /usr/src/librespeed-cli/out/librespeed-cli* /bin/librespeed-cli
|
||||
|
||||
CMD ["/bin/librespeed-cli"]
|
36
go.mod
36
go.mod
|
@ -1,17 +1,27 @@
|
|||
module librespeed-cli
|
||||
module github.com/librespeed/speedtest-cli
|
||||
|
||||
go 1.14
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.24.2
|
||||
|
||||
require (
|
||||
github.com/briandowns/spinner v1.12.0
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/fatih/color v1.10.0 // indirect
|
||||
github.com/go-ping/ping v0.0.0-20210407214646-e4e642a95741
|
||||
github.com/gocarina/gocsv v0.0.0-20210408192840-02d7211d929d
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/stretchr/testify v1.3.0 // indirect
|
||||
github.com/urfave/cli/v2 v2.3.0
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758 // indirect
|
||||
golang.org/x/sys v0.0.0-20210421221651-33663a62ff08 // indirect
|
||||
github.com/briandowns/spinner v1.23.1
|
||||
github.com/go-ping/ping v1.2.0
|
||||
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/urfave/cli/v2 v2.27.4
|
||||
golang.org/x/sys v0.33.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||
github.com/fatih/color v1.17.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||
golang.org/x/net v0.41.0 // indirect
|
||||
golang.org/x/sync v0.15.0 // indirect
|
||||
golang.org/x/term v0.32.0 // indirect
|
||||
)
|
||||
|
|
91
go.sum
91
go.sum
|
@ -1,59 +1,56 @@
|
|||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/briandowns/spinner v1.12.0 h1:72O0PzqGJb6G3KgrcIOtL/JAGGZ5ptOMCn9cUHmqsmw=
|
||||
github.com/briandowns/spinner v1.12.0/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/briandowns/spinner v1.23.1 h1:t5fDPmScwUjozhDj4FA46p5acZWIPXYE30qW2Ptu650=
|
||||
github.com/briandowns/spinner v1.23.1/go.mod h1:LaZeM4wm2Ywy6vO571mvhQNRcWfRUnXOs0RcKV0wYKM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
|
||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/go-ping/ping v0.0.0-20210407214646-e4e642a95741 h1:b0sLP++Tsle+s57tqg5sUk1/OQsC6yMCciVeqNzOcwU=
|
||||
github.com/go-ping/ping v0.0.0-20210407214646-e4e642a95741/go.mod h1:35JbSyV/BYqHwwRA6Zr1uVDm1637YlNOU61wI797NPI=
|
||||
github.com/gocarina/gocsv v0.0.0-20210408192840-02d7211d929d h1:r3mStZSyjKhEcgbJ5xtv7kT5PZw/tDiFBTMgQx2qsXE=
|
||||
github.com/gocarina/gocsv v0.0.0-20210408192840-02d7211d929d/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||
github.com/go-ping/ping v1.2.0 h1:vsJ8slZBZAXNCK4dPcI2PEE9eM9n9RbXbGouVQ/Y4yQ=
|
||||
github.com/go-ping/ping v1.2.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
|
||||
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 h1:FWNFq4fM1wPfcK40yHE5UO3RUdSNPaBC+j3PokzA6OQ=
|
||||
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758 h1:aEpZnXcAmXkd6AvLb2OPt+EN1Zu/8Ne3pCqPjja5PXY=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8=
|
||||
github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ=
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210421221651-33663a62ff08 h1:qyN5bV+96OX8pL78eXDuz6YlDPzCYgdW74H5yE9BoSU=
|
||||
golang.org/x/sys v0.0.0-20210421221651-33663a62ff08/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
26
main.go
26
main.go
|
@ -6,8 +6,8 @@ import (
|
|||
log "github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"librespeed-cli/defs"
|
||||
"librespeed-cli/speedtest"
|
||||
"github.com/librespeed/speedtest-cli/defs"
|
||||
"github.com/librespeed/speedtest-cli/speedtest"
|
||||
)
|
||||
|
||||
// init sets up the essential bits on start up
|
||||
|
@ -20,7 +20,7 @@ func init() {
|
|||
// warn level is for suppress modes
|
||||
// error level is for errors
|
||||
|
||||
log.SetOutput(os.Stdout)
|
||||
log.SetOutput(os.Stderr)
|
||||
log.SetFormatter(formatter)
|
||||
log.SetLevel(log.InfoLevel)
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ func main() {
|
|||
&cli.BoolFlag{
|
||||
Name: defs.OptionNoICMP,
|
||||
Usage: "Do not use ICMP ping. ICMP doesn't work well under Linux\n" +
|
||||
"at this moment, so you might want to disable it",
|
||||
"\tat this moment, so you might want to disable it",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: defs.OptionConcurrent,
|
||||
|
@ -110,7 +110,7 @@ func main() {
|
|||
Name: defs.OptionJSON,
|
||||
Usage: "Suppress verbose output, only show basic information\n" +
|
||||
"\tin JSON format. Speeds listed in bit/s and not\n" +
|
||||
"\t affected by --bytes",
|
||||
"\taffected by --bytes",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: defs.OptionList,
|
||||
|
@ -139,9 +139,13 @@ func main() {
|
|||
Name: defs.OptionSource,
|
||||
Usage: "`SOURCE` IP address to bind to",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: defs.OptionInterface,
|
||||
Usage: "network INTERFACE to bind to",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: defs.OptionTimeout,
|
||||
Usage: "HTTP `TIMEOUT` in seconds",
|
||||
Usage: "HTTP `TIMEOUT` in seconds.",
|
||||
Value: 15,
|
||||
},
|
||||
&cli.IntFlag{
|
||||
|
@ -164,6 +168,11 @@ func main() {
|
|||
Usage: "Use HTTPS instead of HTTP when communicating with\n" +
|
||||
"\tLibreSpeed.org operated servers",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: defs.OptionCACert,
|
||||
Usage: "Use the specified CA certificate PEM bundle file instead\n" +
|
||||
"\tof the system certificate trust store",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: defs.OptionSkipCertVerify,
|
||||
Usage: "Skip verifying SSL certificate for HTTPS connections (self-signed certs)",
|
||||
|
@ -209,6 +218,11 @@ func main() {
|
|||
Usage: "Send a custom message along with the telemetry results.\n" +
|
||||
"\tImplies --" + defs.OptionShare,
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: defs.OptionFwmark,
|
||||
Usage: "firewall mark to set on socket.",
|
||||
Value: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ package report
|
|||
import (
|
||||
"time"
|
||||
|
||||
"librespeed-cli/defs"
|
||||
"github.com/librespeed/speedtest-cli/defs"
|
||||
)
|
||||
|
||||
// JSONReport represents the output data fields in a JSON file
|
||||
|
@ -11,8 +11,8 @@ type JSONReport struct {
|
|||
Timestamp time.Time `json:"timestamp"`
|
||||
Server Server `json:"server"`
|
||||
Client Client `json:"client"`
|
||||
BytesSent int `json:"bytes_sent"`
|
||||
BytesReceived int `json:"bytes_received"`
|
||||
BytesSent uint64 `json:"bytes_sent"`
|
||||
BytesReceived uint64 `json:"bytes_received"`
|
||||
Ping float64 `json:"ping"`
|
||||
Jitter float64 `json:"jitter"`
|
||||
Upload float64 `json:"upload"`
|
||||
|
|
|
@ -8,17 +8,17 @@ import (
|
|||
"math"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
"github.com/gocarina/gocsv"
|
||||
"github.com/librespeed/speedtest-cli/defs"
|
||||
"github.com/librespeed/speedtest-cli/report"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"librespeed-cli/defs"
|
||||
"librespeed-cli/report"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -27,11 +27,14 @@ const (
|
|||
)
|
||||
|
||||
// doSpeedTest is where the actual speed test happens
|
||||
func doSpeedTest(c *cli.Context, servers []defs.Server, telemetryServer defs.TelemetryServer, network string, silent bool) error {
|
||||
func doSpeedTest(c *cli.Context, servers []defs.Server, telemetryServer defs.TelemetryServer, network string, silent bool, noICMP bool) error {
|
||||
if serverCount := len(servers); serverCount > 1 {
|
||||
log.Infof("Testing against %d servers", serverCount)
|
||||
}
|
||||
|
||||
var reps_json []report.JSONReport
|
||||
var reps_csv []report.CSVReport
|
||||
|
||||
// fetch current user's IP info
|
||||
for _, currentServer := range servers {
|
||||
// get telemetry level
|
||||
|
@ -66,7 +69,7 @@ func doSpeedTest(c *cli.Context, servers []defs.Server, telemetryServer defs.Tel
|
|||
}
|
||||
|
||||
// skip ICMP if option given
|
||||
currentServer.NoICMP = c.Bool(defs.OptionNoICMP)
|
||||
currentServer.NoICMP = noICMP
|
||||
|
||||
p, jitter, err := currentServer.ICMPPingAndJitter(pingCount, c.String(defs.OptionSource), network)
|
||||
if err != nil {
|
||||
|
@ -75,13 +78,13 @@ func doSpeedTest(c *cli.Context, servers []defs.Server, telemetryServer defs.Tel
|
|||
}
|
||||
|
||||
if pb != nil {
|
||||
pb.FinalMSG = fmt.Sprintf("Ping: %.0f ms\tJitter: %.0f ms\n", p, jitter)
|
||||
pb.FinalMSG = fmt.Sprintf("Ping: %.2f ms\tJitter: %.2f ms\n", p, jitter)
|
||||
pb.Stop()
|
||||
}
|
||||
|
||||
// get download value
|
||||
var downloadValue float64
|
||||
var bytesRead int
|
||||
var bytesRead uint64
|
||||
if c.Bool(defs.OptionNoDownload) {
|
||||
log.Info("Download test is disabled")
|
||||
} else {
|
||||
|
@ -91,12 +94,12 @@ func doSpeedTest(c *cli.Context, servers []defs.Server, telemetryServer defs.Tel
|
|||
return err
|
||||
}
|
||||
downloadValue = download
|
||||
bytesRead = br
|
||||
bytesRead = uint64(br)
|
||||
}
|
||||
|
||||
// get upload value
|
||||
var uploadValue float64
|
||||
var bytesWritten int
|
||||
var bytesWritten uint64
|
||||
if c.Bool(defs.OptionNoUpload) {
|
||||
log.Info("Upload test is disabled")
|
||||
} else {
|
||||
|
@ -106,16 +109,16 @@ func doSpeedTest(c *cli.Context, servers []defs.Server, telemetryServer defs.Tel
|
|||
return err
|
||||
}
|
||||
uploadValue = upload
|
||||
bytesWritten = bw
|
||||
bytesWritten = uint64(bw)
|
||||
}
|
||||
|
||||
// print result if --simple is given
|
||||
if c.Bool(defs.OptionSimple) {
|
||||
if c.Bool(defs.OptionBytes) {
|
||||
useMebi := c.Bool(defs.OptionMebiBytes)
|
||||
log.Warnf("Ping:\t%.0f ms\tJitter:\t%.0f ms\nDownload rate:\t%s\nUpload rate:\t%s", p, jitter, humanizeMbps(downloadValue, useMebi), humanizeMbps(uploadValue, useMebi))
|
||||
log.Warnf("Ping:\t%.2f ms\tJitter:\t%.2f ms\nDownload rate:\t%s\nUpload rate:\t%s", p, jitter, humanizeMbps(downloadValue, useMebi), humanizeMbps(uploadValue, useMebi))
|
||||
} else {
|
||||
log.Warnf("Ping:\t%.0f ms\tJitter:\t%.0f ms\nDownload rate:\t%.2f Mbps\nUpload rate:\t%.2f Mbps", p, jitter, downloadValue, uploadValue)
|
||||
log.Warnf("Ping:\t%.2f ms\tJitter:\t%.2f ms\nDownload rate:\t%.2f Mbps\nUpload rate:\t%.2f Mbps", p, jitter, downloadValue, uploadValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,34 +143,25 @@ func doSpeedTest(c *cli.Context, servers []defs.Server, telemetryServer defs.Tel
|
|||
// check for --csv or --json. the program prioritize the --csv before the --json. this is the same behavior as speedtest-cli
|
||||
if c.Bool(defs.OptionCSV) {
|
||||
// print csv if --csv is given
|
||||
var reps []report.CSVReport
|
||||
|
||||
var rep report.CSVReport
|
||||
rep.Timestamp = time.Now()
|
||||
|
||||
rep.Name = currentServer.Name
|
||||
rep.Address = u.String()
|
||||
rep.Ping = p
|
||||
rep.Ping = math.Round(p*100) / 100
|
||||
rep.Jitter = math.Round(jitter*100) / 100
|
||||
rep.Download = math.Round(downloadValue*100) / 100
|
||||
rep.Upload = math.Round(uploadValue*100) / 100
|
||||
rep.Share = shareLink
|
||||
rep.IP = ispInfo.RawISPInfo.IP
|
||||
|
||||
reps = append(reps, rep)
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := gocsv.MarshalWithoutHeaders(&reps, &buf); err != nil {
|
||||
log.Errorf("Error generating CSV report: %s", err)
|
||||
} else {
|
||||
log.Warn(buf.String())
|
||||
}
|
||||
reps_csv = append(reps_csv, rep)
|
||||
} else if c.Bool(defs.OptionJSON) {
|
||||
// print json if --json is given
|
||||
var rep report.JSONReport
|
||||
rep.Timestamp = time.Now()
|
||||
|
||||
rep.Ping = p
|
||||
rep.Ping = math.Round(p*100) / 100
|
||||
rep.Jitter = math.Round(jitter*100) / 100
|
||||
rep.Download = math.Round(downloadValue*100) / 100
|
||||
rep.Upload = math.Round(uploadValue*100) / 100
|
||||
|
@ -181,22 +175,34 @@ func doSpeedTest(c *cli.Context, servers []defs.Server, telemetryServer defs.Tel
|
|||
rep.Client = report.Client{ispInfo.RawISPInfo}
|
||||
rep.Client.Readme = ""
|
||||
|
||||
if b, err := json.Marshal(&rep); err != nil {
|
||||
log.Errorf("Error generating JSON report: %s", err)
|
||||
} else {
|
||||
log.Warnf("%s", b)
|
||||
}
|
||||
reps_json = append(reps_json, rep)
|
||||
}
|
||||
} else {
|
||||
log.Infof("Selected server %s (%s) is not responding at the moment, try again later", currentServer.Name, u.Hostname())
|
||||
}
|
||||
|
||||
// add a new line after each test if testing multiple servers
|
||||
if len(servers) > 1 {
|
||||
//add a new line after each test if testing multiple servers
|
||||
if len(servers) > 1 && !silent {
|
||||
log.Warn()
|
||||
}
|
||||
}
|
||||
|
||||
// check for --csv or --json. the program prioritize the --csv before the --json. this is the same behavior as speedtest-cli
|
||||
if c.Bool(defs.OptionCSV) {
|
||||
var buf bytes.Buffer
|
||||
if err := gocsv.MarshalWithoutHeaders(&reps_csv, &buf); err != nil {
|
||||
log.Errorf("Error generating CSV report: %s", err)
|
||||
} else {
|
||||
os.Stdout.WriteString(buf.String())
|
||||
}
|
||||
} else if c.Bool(defs.OptionJSON) {
|
||||
if b, err := json.Marshal(&reps_json); err != nil {
|
||||
log.Errorf("Error generating JSON report: %s", err)
|
||||
} else {
|
||||
os.Stdout.Write(b[:])
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -233,7 +239,7 @@ func sendTelemetry(telemetryServer defs.TelemetryServer, ispInfo *defs.GetIPResu
|
|||
if fPing, err := wr.CreateFormField("ping"); err != nil {
|
||||
log.Debugf("Error creating form field: %s", err)
|
||||
return "", err
|
||||
} else if _, err = fPing.Write([]byte(strconv.Itoa(int(pingVal)))); err != nil {
|
||||
} else if _, err = fPing.Write([]byte(strconv.FormatFloat(pingVal, 'f', 2, 64))); err != nil {
|
||||
log.Debugf("Error writing form field: %s", err)
|
||||
return "", err
|
||||
}
|
||||
|
@ -241,7 +247,7 @@ func sendTelemetry(telemetryServer defs.TelemetryServer, ispInfo *defs.GetIPResu
|
|||
if fJitter, err := wr.CreateFormField("jitter"); err != nil {
|
||||
log.Debugf("Error creating form field: %s", err)
|
||||
return "", err
|
||||
} else if _, err = fJitter.Write([]byte(strconv.Itoa(int(jitter)))); err != nil {
|
||||
} else if _, err = fJitter.Write([]byte(strconv.FormatFloat(jitter, 'f', 2, 64))); err != nil {
|
||||
log.Debugf("Error writing form field: %s", err)
|
||||
return "", err
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package speedtest
|
|||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -19,8 +20,8 @@ import (
|
|||
log "github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"librespeed-cli/defs"
|
||||
"librespeed-cli/report"
|
||||
"github.com/librespeed/speedtest-cli/defs"
|
||||
"github.com/librespeed/speedtest-cli/report"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -64,6 +65,7 @@ func SpeedTest(c *cli.Context) error {
|
|||
|
||||
// print version
|
||||
if c.Bool(defs.OptionVersion) {
|
||||
log.SetOutput(os.Stdout)
|
||||
log.Warnf("%s %s (built on %s)", defs.ProgName, defs.ProgVersion, defs.BuildDate)
|
||||
log.Warn("https://github.com/librespeed/speedtest-cli")
|
||||
log.Warn("Licensed under GNU Lesser General Public License v3.0")
|
||||
|
@ -73,6 +75,10 @@ func SpeedTest(c *cli.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
if c.String(defs.OptionSource) != "" && c.String(defs.OptionInterface) != "" {
|
||||
return fmt.Errorf("incompatible options '%s' and '%s'", defs.OptionSource, defs.OptionInterface)
|
||||
}
|
||||
|
||||
// set CSV delimiter
|
||||
gocsv.TagSeparator = c.String(defs.OptionCSVDelimiter)
|
||||
|
||||
|
@ -80,7 +86,7 @@ func SpeedTest(c *cli.Context) error {
|
|||
if c.Bool(defs.OptionCSVHeader) {
|
||||
var rep []report.CSVReport
|
||||
b, _ := gocsv.MarshalBytes(&rep)
|
||||
log.Warnf("%s", b)
|
||||
os.Stdout.WriteString(string(b))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -99,7 +105,7 @@ func SpeedTest(c *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
if err := json.Unmarshal(b, &telemetryServer); err != nil {
|
||||
log.Errorf("Error parsing %s: %s", err)
|
||||
log.Errorf("Error parsing %s: %s", telemetryJSON, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -137,6 +143,8 @@ func SpeedTest(c *cli.Context) error {
|
|||
return errors.New("invalid concurrent requests setting")
|
||||
}
|
||||
|
||||
noICMP := c.Bool(defs.OptionNoICMP)
|
||||
|
||||
// HTTP requests timeout
|
||||
http.DefaultClient.Timeout = time.Duration(c.Int(defs.OptionTimeout)) * time.Second
|
||||
|
||||
|
@ -154,59 +162,71 @@ func SpeedTest(c *cli.Context) error {
|
|||
}
|
||||
|
||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||||
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: c.Bool(defs.OptionSkipCertVerify)}
|
||||
|
||||
// bind to source IP address if given, or if ipv4/ipv6 is forced
|
||||
if src := c.String(defs.OptionSource); src != "" || (forceIPv4 || forceIPv6) {
|
||||
var localTCPAddr *net.TCPAddr
|
||||
if src != "" {
|
||||
// first we parse the IP to see if it's valid
|
||||
addr, err := net.ResolveIPAddr(network, src)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "no suitable address") {
|
||||
if forceIPv6 {
|
||||
log.Errorf("Address %s is not a valid IPv6 address", src)
|
||||
} else {
|
||||
log.Errorf("Address %s is not a valid IPv4 address", src)
|
||||
}
|
||||
} else {
|
||||
log.Errorf("Error parsing source IP: %s", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Using %s as source IP", src)
|
||||
localTCPAddr = &net.TCPAddr{IP: addr.IP}
|
||||
if caCertFileName := c.String(defs.OptionCACert); caCertFileName != "" {
|
||||
caCert, err := ioutil.ReadFile(caCertFileName)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
caCertPool := x509.NewCertPool()
|
||||
caCertPool.AppendCertsFromPEM(caCert)
|
||||
|
||||
var dialContext func(context.Context, string, string) (net.Conn, error)
|
||||
defaultDialer := &net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
transport.TLSClientConfig = &tls.Config{
|
||||
InsecureSkipVerify: c.Bool(defs.OptionSkipCertVerify),
|
||||
RootCAs: caCertPool,
|
||||
}
|
||||
|
||||
if localTCPAddr != nil {
|
||||
defaultDialer.LocalAddr = localTCPAddr
|
||||
} else {
|
||||
transport.TLSClientConfig = &tls.Config{
|
||||
InsecureSkipVerify: c.Bool(defs.OptionSkipCertVerify),
|
||||
}
|
||||
|
||||
switch {
|
||||
case forceIPv4:
|
||||
dialContext = func(ctx context.Context, network, address string) (conn net.Conn, err error) {
|
||||
return defaultDialer.DialContext(ctx, "tcp4", address)
|
||||
}
|
||||
case forceIPv6:
|
||||
dialContext = func(ctx context.Context, network, address string) (conn net.Conn, err error) {
|
||||
return defaultDialer.DialContext(ctx, "tcp6", address)
|
||||
}
|
||||
default:
|
||||
dialContext = defaultDialer.DialContext
|
||||
}
|
||||
|
||||
// set default HTTP client's Transport to the one that binds the source address
|
||||
// this is modified from http.DefaultTransport
|
||||
transport.DialContext = dialContext
|
||||
}
|
||||
|
||||
dialer := &net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}
|
||||
// bind to source IP address if given
|
||||
if src := c.String(defs.OptionSource); src != "" {
|
||||
var err error
|
||||
dialer, err = newDialerAddressBound(src, network)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// bind to interface if given
|
||||
// bind to interface if given
|
||||
iface := c.String(defs.OptionInterface)
|
||||
fwmark := c.Int(defs.OptionFwmark)
|
||||
|
||||
if iface != "" || fwmark > 0 {
|
||||
var err error
|
||||
dialer, err = newDialerInterfaceOrFwmarkBound(iface, fwmark)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// ICMP ping does not support interface binding.
|
||||
noICMP = true
|
||||
}
|
||||
|
||||
// enforce if ipv4/ipv6 is forced
|
||||
var dialContext func(context.Context, string, string) (net.Conn, error)
|
||||
switch {
|
||||
case forceIPv4:
|
||||
dialContext = func(ctx context.Context, network, address string) (conn net.Conn, err error) {
|
||||
return dialer.DialContext(ctx, "tcp4", address)
|
||||
}
|
||||
case forceIPv6:
|
||||
dialContext = func(ctx context.Context, network, address string) (conn net.Conn, err error) {
|
||||
return dialer.DialContext(ctx, "tcp6", address)
|
||||
}
|
||||
default:
|
||||
dialContext = dialer.DialContext
|
||||
}
|
||||
|
||||
// set default HTTP client's Transport to the one that binds the source address
|
||||
// this is modified from http.DefaultTransport
|
||||
transport.DialContext = dialContext
|
||||
http.DefaultClient.Transport = transport
|
||||
|
||||
// load server list
|
||||
|
@ -257,7 +277,7 @@ func SpeedTest(c *cli.Context) error {
|
|||
|
||||
// if --server is given, do speed tests with all of them
|
||||
if len(c.IntSlice(defs.OptionServer)) > 0 {
|
||||
return doSpeedTest(c, servers, telemetryServer, network, silent)
|
||||
return doSpeedTest(c, servers, telemetryServer, network, silent, noICMP)
|
||||
} else {
|
||||
// else select the fastest server from the list
|
||||
log.Info("Selecting the fastest server based on ping")
|
||||
|
@ -271,7 +291,7 @@ func SpeedTest(c *cli.Context) error {
|
|||
|
||||
// spawn 10 concurrent pingers
|
||||
for i := 0; i < 10; i++ {
|
||||
go pingWorker(jobs, results, &wg, c.String(defs.OptionSource), network, c.Bool(defs.OptionNoICMP))
|
||||
go pingWorker(jobs, results, &wg, c.String(defs.OptionSource), network, noICMP)
|
||||
}
|
||||
|
||||
// send ping jobs to workers
|
||||
|
@ -308,7 +328,7 @@ func SpeedTest(c *cli.Context) error {
|
|||
}
|
||||
|
||||
// do speed test on the server
|
||||
return doSpeedTest(c, []defs.Server{servers[serverIdx]}, telemetryServer, network, silent)
|
||||
return doSpeedTest(c, []defs.Server{servers[serverIdx]}, telemetryServer, network, silent, noICMP)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -453,6 +473,10 @@ func preprocessServers(servers []defs.Server, forceHTTPS bool, excludes, specifi
|
|||
ret = append(ret, server)
|
||||
}
|
||||
}
|
||||
if len(ret) == 0 {
|
||||
error_message := fmt.Sprintf("specified server(s) not found: %v", specific)
|
||||
return nil, errors.New(error_message)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
}
|
||||
|
@ -469,3 +493,31 @@ func contains(arr []int, val int) bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func newDialerAddressBound(src string, network string) (dialer *net.Dialer, err error) {
|
||||
// first we parse the IP to see if it's valid
|
||||
addr, err := net.ResolveIPAddr(network, src)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "no suitable address") {
|
||||
if network == "ip6" {
|
||||
log.Errorf("Address %s is not a valid IPv6 address", src)
|
||||
} else {
|
||||
log.Errorf("Address %s is not a valid IPv4 address", src)
|
||||
}
|
||||
} else {
|
||||
log.Errorf("Error parsing source IP: %s", err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debugf("Using %s as source IP", src)
|
||||
localTCPAddr := &net.TCPAddr{IP: addr.IP}
|
||||
|
||||
defaultDialer := &net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}
|
||||
|
||||
defaultDialer.LocalAddr = localTCPAddr
|
||||
return defaultDialer, nil
|
||||
}
|
||||
|
|
13
speedtest/util.go
Normal file
13
speedtest/util.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package speedtest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
func newDialerInterfaceOrFwmarkBound(iface string, fwmark int) (dialer *net.Dialer, err error) {
|
||||
return nil, fmt.Errorf("cannot bound to interface on this platform")
|
||||
}
|
38
speedtest/util_linux.go
Normal file
38
speedtest/util_linux.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package speedtest
|
||||
|
||||
import (
|
||||
"net"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func newDialerInterfaceOrFwmarkBound(iface string, fwmark int) (dialer *net.Dialer, err error) {
|
||||
// In linux there is the socket option SO_BINDTODEVICE.
|
||||
// Therefore we can really bind the socket to the device instead of binding to the address that
|
||||
// would be affected by the default routes.
|
||||
control := func(network, address string, c syscall.RawConn) error {
|
||||
var errSock error
|
||||
err := c.Control((func(fd uintptr) {
|
||||
if iface != "" {
|
||||
errSock = unix.BindToDevice(int(fd), iface)
|
||||
}
|
||||
|
||||
if fwmark > 0 {
|
||||
errSock = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, fwmark)
|
||||
}
|
||||
}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return errSock
|
||||
}
|
||||
|
||||
dialer = &net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
Control: control,
|
||||
}
|
||||
return dialer, nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue