Skip to content

Commit

Permalink
Re-use probes support - history (#84)
Browse files Browse the repository at this point in the history
  • Loading branch information
radulucut committed Jan 11, 2024
1 parent 3950c73 commit 1f1cadb
Show file tree
Hide file tree
Showing 15 changed files with 619 additions and 87 deletions.
45 changes: 39 additions & 6 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,51 @@ on:

jobs:
build:
runs-on: ubuntu-latest

runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
go: ["1.20"]
os: [ubuntu-latest, macOS-latest, windows-latest]
name: ${{ matrix.os }} Go ${{ matrix.go }} Tests
steps:
- uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: '1.20.x'
go-version: ${{ matrix.go }}
cache: true
- name: Install dependencies
run: go get .
- name: Build
run: go build -v ./...
- name: Run tests
run: go test ./... -v
run: go test ./... -v
- name: Build
run: go build -o bin/
- name: Test windows cmd
if: matrix.os == 'windows-latest'
shell: cmd
run: |
bin\globalping-cli.exe ping cdn.jsdelivr.net
bin\globalping-cli.exe ping cdn.jsdelivr.net from @-1
- name: Test windows powershell
if: matrix.os == 'windows-latest'
shell: powershell
run: |
bin\globalping-cli.exe ping cdn.jsdelivr.net
bin\globalping-cli.exe ping cdn.jsdelivr.net from '@-1'
- name: Test windows bash
if: matrix.os == 'windows-latest'
shell: bash
run: |
./bin/globalping-cli.exe ping cdn.jsdelivr.net
./bin/globalping-cli.exe ping cdn.jsdelivr.net from @-1
- name: Test macOS
if: matrix.os == 'macOS-latest'
run: |
./bin/globalping-cli ping cdn.jsdelivr.net
./bin/globalping-cli ping cdn.jsdelivr.net from @-1
- name: Test ubuntu
if: matrix.os == 'ubuntu-latest'
run: |
./bin/globalping-cli ping cdn.jsdelivr.net
./bin/globalping-cli ping cdn.jsdelivr.net from @-1
54 changes: 52 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ Additional Commands:

Flags:
-C, --ci Disable realtime terminal updates and color suitable for CI and scripting (default false)
-F, --from string Comma-separated list of location values to match against or measurement ID. For example the partial or full name of a continent, region (e.g eastern europe), country, US state, city or network (default "world"). (default "world")
-F, --from string Comma-separated list of location values to match against or a measurement ID
For example, the partial or full name of a continent, region (e.g eastern europe), country, US state, city or network
Or use [@1 | first, @2 ... @-2, @-1 | last | previous] to run with the probes from previous measurements. (default "world")
-h, --help help for globalping
-J, --json Output results in JSON format (default false)
--latency Output only the stats of a measurement (default false). Only applies to the dns, http and ping commands
Expand Down Expand Up @@ -201,7 +203,7 @@ google.com. 300 IN A 142.250.183.206
#### Reselect probes
You can select the same probes used in a previous measurement.
You can select the same probes used in a previous measurement by passing the measurement ID to the `--from` flag.
```bash
globalping dns google.com from rvasVvKnj48cxNjC
Expand All @@ -226,6 +228,54 @@ google.com. 300 IN A 142.250.199.174
;; MSG SIZE rcvd: 55
```
#### Reselect probes from measurements in the current session
Use `[@1 | first, @2 ... @-2, @-1 | last | previous]` to select the probes from previous measurements in the current session.
```bash
globalping ping google.com from USA --latency
> NA, US, (VA), Ashburn, ASN:213230, Hetzner Online GmbH
Min: 7.314 ms
Max: 7.413 ms
Avg: 7.359 ms
globalping ping google.com from Germany --latency
> EU, DE, Falkenstein, ASN:24940, Hetzner Online GmbH
Min: 4.87 ms
Max: 4.936 ms
Avg: 4.911 ms
globalping ping google.com from previous --latency
> EU, DE, Falkenstein, ASN:24940, Hetzner Online GmbH
Min: 4.87 ms
Max: 4.936 ms
Avg: 4.911 ms
globalping ping google.com from @-1 --latency
> EU, DE, Falkenstein, ASN:24940, Hetzner Online GmbH
Min: 4.87 ms
Max: 4.936 ms
Avg: 4.911 ms
globalping ping google.com from @-2 --latency
> NA, US, (VA), Ashburn, ASN:213230, Hetzner Online GmbH
Min: 7.314 ms
Max: 7.413 ms
Avg: 7.359 ms
globalping ping google.com from first --latency
> NA, US, (VA), Ashburn, ASN:213230, Hetzner Online GmbH
Min: 7.314 ms
Max: 7.413 ms
Avg: 7.359 ms
globalping ping google.com from @1 --latency
> NA, US, (VA), Ashburn, ASN:213230, Hetzner Online GmbH
Min: 7.314 ms
Max: 7.413 ms
Avg: 7.359 ms
```
#### 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
174 changes: 170 additions & 4 deletions cmd/common.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,188 @@
package cmd

import (
"bufio"
"errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"

"github.com/icza/backscanner"
"github.com/jsdelivr/globalping-cli/model"
"github.com/shirou/gopsutil/process"
)

func createLocations(from string) []model.Locations {
var (
ErrorNoPreviousMeasurements = errors.New("no previous measurements found")
ErrInvalidIndex = errors.New("invalid index")
ErrIndexOutOfRange = errors.New("index out of range")
)

var SESSION_PATH string

func inProgressUpdates(ci bool) bool {
return !(ci)
}

func createLocations(from string) ([]model.Locations, bool, error) {
fromArr := strings.Split(from, ",")
if len(fromArr) == 1 {
mId, err := mapToMeasurementID(fromArr[0])
if err != nil {
return nil, false, err
}
isPreviousMeasurementId := false
if mId == "" {
mId = strings.TrimSpace(fromArr[0])
} else {
isPreviousMeasurementId = true
}
return []model.Locations{
{
Magic: mId,
},
}, isPreviousMeasurementId, nil
}
locations := make([]model.Locations, len(fromArr))
for i, v := range fromArr {
locations[i] = model.Locations{
Magic: strings.TrimSpace(v),
}
}
return locations
return locations, false, nil
}

func inProgressUpdates(ci bool) bool {
return !(ci)
// Maps a location to a measurement ID if possible
func mapToMeasurementID(location string) (string, error) {
if location == "" {
return "", nil
}
if location[0] == '@' {
index, err := strconv.Atoi(location[1:])
if err != nil {
return "", ErrInvalidIndex
}
return getMeasurementID(index)
}
if location == "first" {
return getMeasurementID(1)
}
if location == "last" || location == "previous" {
return getMeasurementID(-1)
}
return "", nil
}

// Returns the measurement ID at the given index from the session history
func getMeasurementID(index int) (string, error) {
if index == 0 {
return "", ErrInvalidIndex
}
f, err := os.Open(getMeasurementsPath())
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return "", ErrorNoPreviousMeasurements
}
return "", fmt.Errorf("failed to open previous measurements file: %s", err)
}
defer f.Close()
// Read ids from the end of the file
if index < 0 {
fStats, err := f.Stat()
if err != nil {
return "", fmt.Errorf("failed to read previous measurements: %s", err)
}
if fStats.Size() == 0 {
return "", ErrorNoPreviousMeasurements
}
scanner := backscanner.New(f, int(fStats.Size()-1)) // -1 to skip last newline
for {
index++
b, _, err := scanner.LineBytes()
if err != nil {
if err == io.EOF {
return "", ErrIndexOutOfRange
}
return "", fmt.Errorf("failed to read previous measurements: %s", err)
}
if index == 0 {
return string(b), nil
}
}
}
// Read ids from the beginning of the file
scanner := bufio.NewScanner(f)
for scanner.Scan() {
index--
if index == 0 {
return scanner.Text(), nil
}
}
if err := scanner.Err(); err != nil {
return "", fmt.Errorf("failed to read previous measurements: %s", err)
}
return "", ErrIndexOutOfRange
}

// Saves the measurement ID to the session history
func saveMeasurementID(id string) error {
_, err := os.Stat(getSessionPath())
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
err := os.Mkdir(getSessionPath(), 0755)
if err != nil {
return fmt.Errorf("failed to save measurement ID: %s", err)
}
} else {
return fmt.Errorf("failed to save measurement ID: %s", err)
}
}
f, err := os.OpenFile(getMeasurementsPath(), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return fmt.Errorf("failed to save measurement ID: %s", err)
}
defer f.Close()
_, err = f.WriteString(id + "\n")
if err != nil {
return fmt.Errorf("failed to save measurement ID: %s", err)
}
return nil
}

func getSessionPath() string {
if SESSION_PATH != "" {
return SESSION_PATH
}
SESSION_PATH = filepath.Join(os.TempDir(), getSessionId())
return SESSION_PATH
}

func getSessionId() string {
p, err := process.NewProcess(int32(os.Getppid()))
if err != nil {
return "globalping"
}
// Workaround for bash.exe on Windows
// PPID is different on each run.
// https://cygwin.com/git/gitweb.cgi?p=newlib-cygwin.git;a=commit;h=448cf5aa4b429d5a9cebf92a0da4ab4b5b6d23fe
if runtime.GOOS == "windows" {
name, _ := p.Name()
if name == "bash.exe" {
p, err = p.Parent()
if err != nil {
return "globalping"
}
}
}
createTime, _ := p.CreateTime()
return fmt.Sprintf("globalping_%d_%d", p.Pid, createTime)
}

func getMeasurementsPath() string {
return filepath.Join(getSessionPath(), "measurements")
}
Loading

0 comments on commit 1f1cadb

Please sign in to comment.