Skip to content

Commit

Permalink
feat: update model
Browse files Browse the repository at this point in the history
  • Loading branch information
b4nst committed Sep 5, 2024
1 parent 57394dc commit 942a629
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 30 deletions.
21 changes: 10 additions & 11 deletions cmd/icmperf/main.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package main

import (
"fmt"
"time"

"github.com/alecthomas/kong"
tea "github.com/charmbracelet/bubbletea"
"github.com/prometheus-community/pro-bing"

"github.com/b4nst/icmperf/pkg/cli"
"github.com/b4nst/icmperf/pkg/recorder"
"github.com/b4nst/icmperf/pkg/model"
"github.com/b4nst/icmperf/pkg/session"
)

Expand All @@ -23,6 +24,7 @@ func main() {
if cli.Duration > 0 {
p.Timeout = cli.Duration
} else {
cli.Duration = time.Duration(cli.Count * int(time.Second))
p.Count = cli.Count
}
p.SetPrivileged(cli.Privileged)
Expand All @@ -44,15 +46,12 @@ func main() {
}

s := session.NewSession(pingers)
ktx.FatalIfErrorf(s.Run())
m := model.NewModel(s, cli.Target, p.IPAddr().String(), cli.Duration+time.Second)

stats := s.Statistics()
stat, err := recorder.ProcessStats(stats)
ktx.FatalIfErrorf(err)

for _, s := range stats {
fmt.Printf("%s\n", s)
go func() {
s.Run()
}()
if _, err := tea.NewProgram(m).Run(); err != nil {
ktx.FatalIfErrorf(err)
}
fmt.Println("- - - - - - - - - - - - - - - - - - - - - - - - -")
fmt.Printf("%s\n", stat)
}
86 changes: 67 additions & 19 deletions pkg/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,55 @@ package model

import (
"fmt"
"net"
"sort"
"strings"
"time"

"github.com/charmbracelet/bubbles/progress"
"github.com/charmbracelet/bubbles/table"
"github.com/charmbracelet/bubbles/timer"
tea "github.com/charmbracelet/bubbletea"
"github.com/dustin/go-humanize"

"github.com/b4nst/icmperf/pkg/pinger"
"github.com/b4nst/icmperf/pkg/recorder"
"github.com/b4nst/icmperf/pkg/session"
)

var (
maxWidth = 100
)

type Model struct {
record *recorder.Record
payload []byte

session *session.Session
duration time.Duration
peer string
peerAddr string

timer timer.Model
progress progress.Model
table table.Model
}

func NewModel(pinger *pinger.Pinger, record *recorder.Record, peer *net.UDPAddr, mtu int, duration time.Duration) *Model {
func NewModel(session *session.Session, peer string, peerAddr string, duration time.Duration) *Model {
return &Model{
record: record,
payload: make([]byte, mtu-28),

session: session,
duration: duration,
peer: peer,
peerAddr: peerAddr,

timer: timer.NewWithInterval(duration, 200*time.Millisecond),
progress: progress.New(progress.WithFillCharacters('▱', ' '), progress.WithDefaultGradient()),
table: table.New(table.WithFocused(false), table.WithColumns([]table.Column{
{Title: "Seq", Width: 10},
{Title: "Latency", Width: 10},
{Title: "RTT", Width: 10},
{Title: "Bitrate", Width: 20},
})),
}
}

type pingTick time.Time
type statsMsg []table.Row
type statsErr error

func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
cmds := []tea.Cmd{}
Expand All @@ -49,16 +60,24 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if msg.String() == "ctrl+c" {
cmds = append(cmds, tea.Quit)
}
case timer.TimeoutMsg:
cmds = append(cmds, tea.Quit)
case timer.TickMsg:
var tc, pc tea.Cmd
m.timer, tc = m.timer.Update(msg)
pc = m.progress.SetPercent(1.0 - m.timer.Timeout.Seconds()/m.duration.Seconds())
cmds = append(cmds, tc, pc)
case tea.WindowSizeMsg:
m.progress.Width = min(msg.Width-4, maxWidth)
case error:
fmt.Println(msg)
cmds = append(cmds, tea.Quit)
case statsMsg:
var cmd tea.Cmd
m.table.SetRows(msg)
m.table.SetHeight(len(msg) + 2)
m.table.GotoBottom()
m.table, cmd = m.table.Update(msg)
cmds = append(cmds, cmd, m.statsCmd())
case statsErr:
cmds = append(cmds, m.statsCmd())
case progress.FrameMsg:
progressModel, cmd := m.progress.Update(msg)
m.progress = progressModel.(progress.Model)
Expand All @@ -71,19 +90,48 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
func (m *Model) View() string {
view := strings.Builder{}

view.WriteString("Probe size: ")
view.WriteString(humanize.Bytes(uint64(len(m.payload))))
view.WriteString(" ")
view.WriteString(fmt.Sprintf("Connecting to host %s (%s)\n", m.peer, m.peerAddr))

view.WriteString("ETC: ")
view.WriteString(m.timer.View())
view.WriteString("\n")

view.WriteString(" ")
view.WriteString(m.progress.View())
view.WriteString("\n\n")

view.WriteString(m.table.View())

return view.String()
}

func (m *Model) Init() tea.Cmd {
return tea.Batch(m.timer.Init())
return tea.Batch(m.timer.Init(), m.statsCmd())
}

func (m *Model) statsCmd() tea.Cmd {
return func() tea.Msg {
stats := m.session.Statistics()
avg, err := recorder.ProcessStats(stats)
if err != nil {
return statsErr(err)
}
// Sort by sequence number
sort.Sort(recorder.BySeq(stats))
rows := make([]table.Row, 0, len(stats))
for _, stat := range stats {
rows = append(rows, table.Row{
fmt.Sprintf("%d", stat.Seq),
fmt.Sprintf("%s", stat.Latency.Truncate(time.Millisecond)),
fmt.Sprintf("%s", stat.Rtt.Truncate(time.Millisecond)),
fmt.Sprintf("%s/sec", humanize.Bytes(uint64(stat.Bandwidth))),
})
}
rows = append(rows, table.Row{
"Average",
fmt.Sprintf("%s", avg.Latency.Truncate(time.Millisecond)),
fmt.Sprintf("%s", avg.Rtt.Truncate(time.Millisecond)),
fmt.Sprintf("%s/sec", humanize.Bytes(uint64(avg.Bandwidth))),
})

return statsMsg(rows)
}
}
7 changes: 7 additions & 0 deletions pkg/recorder/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,10 @@ func Average(stats []*Stat) *Stat {
total.Bandwidth /= float64(len(stats))
return &total
}

// BySeq implements sort.Interface for []*Stat based on the Seq field.
type BySeq []*Stat

func (a BySeq) Len() int { return len(a) }
func (a BySeq) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a BySeq) Less(i, j int) bool { return a[i].Seq < a[j].Seq }

0 comments on commit 942a629

Please sign in to comment.