Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Infinite ping #86

Merged
merged 27 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f577a85
Add --infinite flag
radulucut Jan 15, 2024
3bbc717
Add infinite output view & refactoring
radulucut Jan 17, 2024
ca86281
Fix Sent count
radulucut Jan 17, 2024
b5da380
More docs
jimaek Jan 17, 2024
b9e9d4e
Add tests + refactoring
radulucut Jan 18, 2024
20c83e7
Update formating, fix test
radulucut Jan 18, 2024
0c774f7
Fix test
radulucut Jan 18, 2024
1ae9a0f
Update Go version and dependencies
radulucut Jan 19, 2024
f527b86
Update table format
radulucut Jan 19, 2024
a99d83d
Add table truncation
radulucut Jan 23, 2024
2e3e18d
Fix limit flag
radulucut Jan 23, 2024
a291e5f
Parse and handle in-progress measurement
radulucut Jan 25, 2024
225d384
Add more tests
radulucut Jan 26, 2024
45d9151
Add support for share & summary + fixes & tests
radulucut Jan 30, 2024
8542e5a
Add combined mdev, change --infinite + --latency
radulucut Jan 31, 2024
180a8ba
Update ping.go
MartinKolarik Feb 2, 2024
3ca1801
Update mdev logic & term padding, fix history
radulucut Feb 5, 2024
940e0a4
Fix tests
radulucut Feb 5, 2024
416ea8f
Refactor code structure to improve testability
radulucut Feb 6, 2024
c741fd5
Add printer
radulucut Feb 7, 2024
7881888
Refactoring, ping tests & fixes
radulucut Feb 7, 2024
ea5c2b8
Skip test on windows
radulucut Feb 7, 2024
96a19a8
Update actions
MartinKolarik Feb 12, 2024
877c51a
Handle failed measurements
radulucut Feb 12, 2024
5d3121e
Add time for in progress measurements
radulucut Feb 13, 2024
b80540d
Replace github.com/golang/mock with go.uber.org/mock
radulucut Feb 13, 2024
9be6fae
Update sleep durations in ping_test.go
radulucut Feb 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
push:
# run only against tags
tags:
- '*'
- "*"

permissions:
contents: write
Expand All @@ -22,12 +22,12 @@ jobs:

- uses: actions/setup-go@v3
with:
go-version: '>=1.20.0'
go-version: ">=1.21.3"
cache: true

- uses: goreleaser/goreleaser-action@v4
with:
version: v1.20.0
version: v1.21.3
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GHTOKEN_GORELEASER }}
Expand All @@ -50,7 +50,7 @@ jobs:
path: |
dist/globalping_Windows_arm64.zip
dist/globalping_Windows_x86_64.zip
dist/globalping_Windows_i386.zip
dist/globalping_Windows_i386.zip

deploy:
needs: goreleaser
Expand All @@ -65,7 +65,7 @@ jobs:
- uses: actions/download-artifact@v3
with:
name: goreleaser-windows

- run: echo "VERSION_NAME=${GITHUB_REF_NAME:1}" >> $GITHUB_ENV
- run: ls -la

Expand All @@ -82,7 +82,7 @@ jobs:
uses: vedantmgoyal2009/winget-releaser@v2
with:
identifier: jsdelivr.Globalping
installers-regex: 'Windows_(arm64|x86_64|i386).zip'
installers-regex: "Windows_(arm64|x86_64|i386).zip"
version: ${{ env.VERSION_NAME }}
max-versions-to-keep: 5
token: ${{ secrets.GHTOKEN_WINGET }}
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
strategy:
fail-fast: false
matrix:
go: ["1.20"]
go: ["1.21"]
os: [ubuntu-latest, macOS-latest, windows-latest]
name: ${{ matrix.os }} Go ${{ matrix.go }} Tests
steps:
Expand Down
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,38 @@ Max: 7.413 ms
Avg: 7.359 ms
```

#### Continuous non-stop measurements

> [!IMPORTANT]
> Currently this feature is limited to the ping command

You can use the `--infinite` flag to continuously ping a host, just like on Linux or MacOS.
Note that while it looks like a single measurement, in actuality its multiple measurements from the same probes combined into a single output.
This means that eventually you will run out of credits and the test will stop.

```bash
globalping ping cdn.jsdelivr.net from Europe --infinite
> EU, GB, London, ASN:40676, Psychz Networks
PING cdn.jsdelivr.net (151.101.1.229) 56(84) bytes of data.
64 bytes from 151.101.1.229 (151.101.1.229): icmp_seq=1 ttl=59 time=0.54 ms
64 bytes from 151.101.1.229 (151.101.1.229): icmp_seq=2 ttl=59 time=0.42 ms
^C
```

If you select multiple probes when using `--infinite` the output will change to a summary comparison table.

```bash
globalping ping cdn.jsdelivr.net from Europe --limit 5 --infinite
Location | Loss | Sent | Last | Avg | Min | Max
EU, NL, Dronten, ASN:41608, NextGenWebs, S.L. | 0.00% | 12 | 25.3 ms | 25.3 ms | 25.2 ms | 25.9 ms
EU, GB, London, ASN:200904, FoxCloud LLP | 0.00% | 12 | 3.21 ms | 2.90 ms | 2.32 ms | 3.27 ms
EU, AT, Vienna, ASN:9009, M247 Europe SRL | 0.00% | 12 | 6.60 ms | 3.85 ms | 0.30 ms | 11.1 ms
EU, GB, London, ASN:60841, BerryByte Limited | 0.00% | 12 | 0.38 ms | 0.41 ms | 0.34 ms | 0.64 ms
EU, NL, Amsterdam, ASN:199959, GWY IT PTY LTD | 0.00% | 12 | 2.07 ms | 2.32 ms | 1.71 ms | 4.56 ms

^C
```

#### Learn about available flags

Most commands have shared and unique flags. We recommend that you familiarize yourself with these so that you can run and automate your network tests in powerful ways.
Expand Down
45 changes: 32 additions & 13 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
)

var ApiUrl = "https://api.globalping.io/v1/measurements"
var PacketsMax = 16

// Post measurement to Globalping API - boolean indicates whether to print CLI help on error
func PostAPI(measurement model.PostMeasurement) (model.PostResponse, bool, error) {
Expand Down Expand Up @@ -88,20 +89,38 @@ func PostAPI(measurement model.PostMeasurement) (model.PostResponse, bool, error
return data, false, nil
}

func DecodeTimings(cmd string, timings json.RawMessage) (model.Timings, error) {
var data model.Timings
func DecodeDNSTimings(timings json.RawMessage) (*model.DNSTimings, error) {
t := &model.DNSTimings{}
err := json.Unmarshal(timings, t)
if err != nil {
return nil, errors.New("invalid timings format returned (other)")
}
return t, nil
}

if cmd == "ping" {
err := json.Unmarshal(timings, &data.Arr)
if err != nil {
return model.Timings{}, errors.New("invalid timings format returned (ping)")
}
} else {
err := json.Unmarshal(timings, &data.Interface)
if err != nil {
return model.Timings{}, errors.New("invalid timings format returned (other)")
}
func DecodeHTTPTimings(timings json.RawMessage) (*model.HTTPTimings, error) {
t := &model.HTTPTimings{}
err := json.Unmarshal(timings, t)
if err != nil {
return nil, errors.New("invalid timings format returned (other)")
}
return t, nil
}

return data, nil
func DecodePingTimings(timings json.RawMessage) ([]model.PingTiming, error) {
t := []model.PingTiming{}
err := json.Unmarshal(timings, &t)
if err != nil {
return nil, errors.New("invalid timings format returned (ping)")
}
return t, nil
}

func DecodePingStats(stats json.RawMessage) (*model.PingStats, error) {
s := &model.PingStats{}
err := json.Unmarshal(stats, s)
if err != nil {
return nil, errors.New("invalid stats format returned")
}
return s, nil
}
46 changes: 27 additions & 19 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,18 @@ func testPostValidation(t *testing.T) {
client.ApiUrl = server.URL

_, showHelp, err := client.PostAPI(opts)
assert.EqualError(t, err, `invalid parameters

// Key order is not guaranteed
expectedErrV1 := `invalid parameters
- "measurement" does not match any of the allowed types
- "target" does not match any of the allowed types
Please check the help for more information`
if err.Error() != expectedErrV1 {
assert.EqualError(t, err, `invalid parameters
- "target" does not match any of the allowed types
- "measurement" does not match any of the allowed types
Please check the help for more information`)
}
assert.True(t, showHelp)
}

Expand Down Expand Up @@ -239,13 +247,15 @@ func testGetPing(t *testing.T) {

assert.Equal(t, "PING", res.Results[0].Result.RawOutput)
assert.Equal(t, "1.1.1.1", res.Results[0].Result.ResolvedAddress)
assert.Equal(t, 27.088, res.Results[0].Result.Stats["avg"])
assert.Equal(t, 28.193, res.Results[0].Result.Stats["max"])
assert.Equal(t, 24.891, res.Results[0].Result.Stats["min"])
assert.Equal(t, float64(3), res.Results[0].Result.Stats["total"])
assert.Equal(t, float64(3), res.Results[0].Result.Stats["rcv"])
assert.Equal(t, float64(0), res.Results[0].Result.Stats["loss"])
assert.Equal(t, float64(0), res.Results[0].Result.Stats["drop"])
stats, err := client.DecodePingStats(res.Results[0].Result.StatsRaw)
assert.NoError(t, err)
assert.Equal(t, float64(27.088), stats.Avg)
assert.Equal(t, float64(28.193), stats.Max)
assert.Equal(t, float64(24.891), stats.Min)
assert.Equal(t, 3, stats.Total)
assert.Equal(t, 3, stats.Rcv)
assert.Equal(t, 0, stats.Drop)
assert.Equal(t, float64(0), stats.Loss)
}

func testGetTraceroute(t *testing.T) {
Expand Down Expand Up @@ -415,9 +425,8 @@ func testGetDns(t *testing.T) {
assert.IsType(t, json.RawMessage{}, res.Results[0].Result.TimingsRaw)

// Test timings
timings, _ := client.DecodeTimings("dns", res.Results[0].Result.TimingsRaw)
assert.Equal(t, float64(15), timings.Interface["total"])
assert.Nil(t, timings.Arr)
timings, _ := client.DecodeDNSTimings(res.Results[0].Result.TimingsRaw)
assert.Equal(t, float64(15), timings.Total)
}

func testGetMtr(t *testing.T) {
Expand Down Expand Up @@ -647,12 +656,11 @@ func testGetHttp(t *testing.T) {
assert.IsType(t, json.RawMessage{}, res.Results[0].Result.TimingsRaw)

// Test timings
timings, _ := client.DecodeTimings("dns", res.Results[0].Result.TimingsRaw)
assert.Nil(t, timings.Arr)
assert.Equal(t, float64(583), timings.Interface["total"])
assert.Equal(t, float64(18), timings.Interface["download"])
assert.Equal(t, float64(450), timings.Interface["firstByte"])
assert.Equal(t, float64(24), timings.Interface["dns"])
assert.Equal(t, float64(70), timings.Interface["tls"])
assert.Equal(t, float64(19), timings.Interface["tcp"])
timings, _ := client.DecodeHTTPTimings(res.Results[0].Result.TimingsRaw)
assert.Equal(t, 583, timings.Total)
assert.Equal(t, 18, timings.Download)
assert.Equal(t, 450, timings.FirstByte)
assert.Equal(t, 24, timings.DNS)
assert.Equal(t, 70, timings.TLS)
assert.Equal(t, 19, timings.TCP)
}
83 changes: 51 additions & 32 deletions cmd/ping.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,55 +39,74 @@ Examples:
ping jsdelivr.com from aws+montreal --latency

# Ping jsdelivr.com from a probe in ASN 123 with json output
ping jsdelivr.com from 123 --json`,
ping jsdelivr.com from 123 --json

# Continuously ping google.com from New York
ping google.com from New York --infinite`,
RunE: func(cmd *cobra.Command, args []string) error {
// Create context
err := createContext(cmd.CalledAs(), args)
if err != nil {
return err
}

// Make post struct
opts = model.PostMeasurement{
Type: "ping",
Target: ctx.Target,
Limit: ctx.Limit,
InProgressUpdates: inProgressUpdates(ctx.CI),
Options: &model.MeasurementOptions{
Packets: packets,
},
if ctx.Infinite {
ctx.Packets = 1
for {
ctx.From, err = ping(cmd)
if err != nil {
return err
}
}
}
radulucut marked this conversation as resolved.
Show resolved Hide resolved
isPreviousMeasurementId := false
opts.Locations, isPreviousMeasurementId, err = createLocations(ctx.From)
if err != nil {
_, err = ping(cmd)
return err
},
}

func ping(cmd *cobra.Command) (string, error) {
opts = model.PostMeasurement{
Type: "ping",
Target: ctx.Target,
Limit: ctx.Limit,
InProgressUpdates: inProgressUpdates(ctx.CI),
Options: &model.MeasurementOptions{
Packets: ctx.Packets,
},
}
locations, isPreviousMeasurementId, err := createLocations(ctx.From)
if err != nil {
cmd.SilenceUsage = true
return "", err
}
opts.Locations = locations

res, showHelp, err := client.PostAPI(opts)
if err != nil {
if !showHelp {
cmd.SilenceUsage = true
return err
}
return "", err
}

res, showHelp, err := client.PostAPI(opts)
// Save measurement ID to history
if !isPreviousMeasurementId {
err := saveMeasurementID(res.ID)
if err != nil {
if !showHelp {
cmd.SilenceUsage = true
}
return err
}

// Save measurement ID to history
if !isPreviousMeasurementId {
err := saveMeasurementID(res.ID)
if err != nil {
fmt.Printf("Warning: %s\n", err)
}
fmt.Printf("Warning: %s\n", err)
}
}

if ctx.Infinite {
err = view.OutputInfinite(res.ID, &ctx)
} else {
view.OutputResults(res.ID, ctx, opts)
return nil
},
}
return res.ID, err
}

func init() {
rootCmd.AddCommand(pingCmd)

// ping specific flags
pingCmd.Flags().IntVar(&packets, "packets", 0, "Specifies the desired amount of ECHO_REQUEST packets to be sent (default 3)")
pingCmd.Flags().IntVar(&ctx.Packets, "packets", 0, "Specifies the desired amount of ECHO_REQUEST packets to be sent (default 3)")
pingCmd.Flags().BoolVar(&ctx.Infinite, "infinite", false, "Continuously send ping request to a target (default false)")
}
Loading