From bac1fc8598a53bd348d5b1b432a28534770c6f48 Mon Sep 17 00:00:00 2001 From: AndrewLester Date: Sat, 15 Jul 2023 14:06:05 -0500 Subject: [PATCH] Finish system panel --- cmd/ntpal/control.go | 96 +++++++++++++++++++++++++++++++---------- internal/ntp/ntp.go | 7 +++ pkg/ntpal/ntpal.go | 100 +++++++++++++++++++++++-------------------- 3 files changed, 134 insertions(+), 69 deletions(-) diff --git a/cmd/ntpal/control.go b/cmd/ntpal/control.go index d5fc675..9ccc642 100644 --- a/cmd/ntpal/control.go +++ b/cmd/ntpal/control.go @@ -11,13 +11,20 @@ import ( "github.com/AndrewLester/ntpal/internal/ntp" "github.com/AndrewLester/ntpal/internal/ui" + "github.com/charmbracelet/bubbles/paginator" "github.com/charmbracelet/bubbles/table" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" ) func handleNTPalUI(socket string) { - m := ntpalUIModel{socket: socket, associationTable: setupAssociationTable(), detailTable: setupDetailTable()} + m := ntpalUIModel{ + socket: socket, + associationTable: setupAssociationTable(), + systemTable: setupDetailTable(), + detailTable: setupDetailTable(), + paginator: setupPaginator(), + } if _, err := tea.NewProgram(m).Run(); err != nil { log.Fatal(err) @@ -31,6 +38,8 @@ type ntpalUIModel struct { associationTable table.Model detailTable table.Model + systemTable table.Model + paginator paginator.Model daemonKillStatus string association *ntp.Association RPCInfo @@ -67,13 +76,13 @@ func fetchInfoCommand(m ntpalUIModel) tea.Cmd { err := (<-assocCall.Done).Error if err != nil { - fmt.Printf("Error getting info from daemon: %v", err) + fmt.Printf("Error getting info from daemon: %v\n", err) os.Exit(1) } err = (<-systemCall.Done).Error if err != nil { - log.Printf("Error getting info from daemon: %v", err) + fmt.Printf("Error getting info from daemon: %v\n", err) os.Exit(1) } @@ -128,6 +137,10 @@ func (m ntpalUIModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.detailTable.SetHeight(1) return m, fetchInfoCommand(m) + case "right", "left": + var cmd tea.Cmd + m.paginator, cmd = m.paginator.Update(msg) + return m, cmd case "stop", "s": m.daemonKillStatus = "Stopping ntpald" return m, tea.Sequence(stopDaemonCommand(), tea.Quit) @@ -156,27 +169,45 @@ func (m ntpalUIModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // Detail table if m.association != nil { - rows := []table.Row{} - rows = append(rows, - table.Row{"Hostname", m.association.Hostname}, - table.Row{"IP", m.association.Srcaddr.IP.String()}, - table.Row{"Offset (ms)", ui.TableFloat(m.association.Offset * 1e3)}, - table.Row{"Poll", (time.Duration(ntp.Log2ToDouble(int8(math.Max(float64(m.association.Poll), float64(m.association.Hpoll))))) * time.Second).String()}, - table.Row{"Reach", strconv.FormatUint(uint64(m.association.Reach), 2)}, - table.Row{"Root delay", ui.TableFloat(m.association.Rootdelay)}, - table.Row{"Root dispersion", ui.TableFloat(m.association.Rootdisp)}, - table.Row{"Delay", ui.TableFloat(m.association.Delay)}, - table.Row{"Dispersion", ui.TableFloat(m.association.Disp)}, - table.Row{"initial burst", strconv.FormatBool(m.association.IburstEnabled)}, - table.Row{"burst", strconv.FormatBool(m.association.BurstEnabled)}, - ) + rows := []table.Row{ + {"Hostname", m.association.Hostname}, + {"IP", m.association.Srcaddr.IP.String()}, + {"Offset (ms)", ui.TableFloat(m.association.Offset * 1e3)}, + {"Poll", (time.Duration(ntp.Log2ToDouble(int8(math.Max(float64(m.association.Poll), float64(m.association.Hpoll))))) * time.Second).String()}, + {"Reach", strconv.FormatUint(uint64(m.association.Reach), 2)}, + {"Root delay", ui.TableFloat(m.association.Rootdelay)}, + {"Root dispersion", ui.TableFloat(m.association.Rootdisp)}, + {"Delay", ui.TableFloat(m.association.Delay)}, + {"Dispersion", ui.TableFloat(m.association.Disp)}, + {"initial burst", strconv.FormatBool(m.association.IburstEnabled)}, + {"burst", strconv.FormatBool(m.association.BurstEnabled)}, + } m.detailTable.SetRows(rows) m.detailTable.SetHeight(len(rows)) } + // System table + peerText := "NONE" + if m.system.Association != nil { + peerText = m.system.Association.Srcaddr.String() + } + systemRows := []table.Row{ + {"Peer", peerText}, + {"Offset", ui.TableFloat(m.system.Offset)}, + {"Jitter", ui.TableFloat(m.system.Jitter)}, + {"Root delay", ui.TableFloat(m.system.Rootdelay)}, + {"Root dispersion", ui.TableFloat(m.system.Rootdisp)}, + {"Clock time", strconv.FormatUint(m.system.Clock.T, 10)}, + {"Clock offset", ui.TableFloat(m.system.Clock.Offset)}, + {"Clock frequency", ui.TableFloat(m.system.Clock.Freq)}, + {"Clock State", strconv.Itoa(m.system.Clock.State)}, + } + m.systemTable.SetRows(systemRows) + m.systemTable.SetHeight(len(systemRows)) + // Association table m.associationTable.SetHeight(len(m.associations)) - rows := []table.Row{} + associationRows := []table.Row{} for _, association := range m.associations { row := table.Row{ association.Srcaddr.IP.String(), @@ -185,9 +216,9 @@ func (m ntpalUIModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { ui.TableFloat(association.Jitter * 1e3), fmt.Sprintf("%s ago", time.Duration(uint64(float64(m.system.Clock.T)-association.Update))*time.Second), } - rows = append(rows, row) + associationRows = append(associationRows, row) } - m.associationTable.SetRows(rows) + m.associationTable.SetRows(associationRows) return m, nil case tickMsg: @@ -198,12 +229,21 @@ func (m ntpalUIModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } func (m ntpalUIModel) View() (s string) { - s += ui.Title("NTPal") + "\n" + pageTitle := "Associations" + if m.paginator.OnLastPage() { + pageTitle = "System" + } + s += ui.Title(fmt.Sprintf("NTPal - %s", pageTitle)) + "\n" if m.association != nil { s += ui.TableBase(m.detailTable.View()) + "\n\n" } else { - s += ui.TableBase(m.associationTable.View()) + "\n\n" + if m.paginator.OnLastPage() { + s += ui.TableBase(m.systemTable.View()) + "\n" + } else { + s += ui.TableBase(m.associationTable.View()) + "\n" + } + s += lipgloss.PlaceHorizontal(85, lipgloss.Center, m.paginator.View()) + "\n\n" } if m.daemonKillStatus != "" { @@ -217,11 +257,21 @@ func (m ntpalUIModel) View() (s string) { if m.association != nil { historyAction = "back" } - s += ui.Help(fmt.Sprintf("q: %s, esc: %s, s: stop daemon", historyAction, tableAction)) + "\n" + s += ui.Help(fmt.Sprintf("q: %s, esc: %s, ←/→ page, s: stop daemon", historyAction, tableAction)) + "\n" } return } +func setupPaginator() paginator.Model { + p := paginator.New() + p.Type = paginator.Dots + p.PerPage = 1 + p.ActiveDot = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "235", Dark: "252"}).Render("•") + p.InactiveDot = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "250", Dark: "238"}).Render("•") + p.SetTotalPages(2) + return p +} + func setupAssociationTable() table.Model { columns := []table.Column{ {Title: "Address", Width: 20}, diff --git a/internal/ntp/ntp.go b/internal/ntp/ntp.go index 85ab63c..e2e9c60 100644 --- a/internal/ntp/ntp.go +++ b/internal/ntp/ntp.go @@ -2,6 +2,13 @@ package ntp type System struct { Clock *Clock + + Association *Association + Offset float64 + Jitter float64 + Leap byte + Rootdelay float64 + Rootdisp float64 } type Clock struct { diff --git a/pkg/ntpal/ntpal.go b/pkg/ntpal/ntpal.go index cedc7f1..49ae503 100644 --- a/pkg/ntpal/ntpal.go +++ b/pkg/ntpal/ntpal.go @@ -115,7 +115,9 @@ var dispatchTable = [][]DispatchCode{ } type NTPalSystem struct { - Clock Clock + ntp.System + Clock Clock // Override value of "Clock" + Association *Association // Override value of "Association" address *net.UDPAddr t ntp.TimestampEncoded /* update time */ @@ -123,18 +125,12 @@ type NTPalSystem struct { stratum byte /* stratum */ poll int8 /* poll interval */ precision int8 /* precision */ - rootdelay float64 /* root delay */ - rootdisp float64 /* root dispersion */ refid ntp.ShortEncoded /* reference ID */ reftime ntp.TimestampEncoded /* reference time */ // Max of the two below is NMAX, but using slice type becaues it's not always full, and nils cant be sorted - m []Chime /* chime list */ - v []Survivor /* survivor list */ - association *Association /* association ID */ - offset float64 /* combined offset */ - jitter float64 /* combined jitter */ - flags int /* option flags */ - n int /* number of survivors */ + m []Chime /* chime list */ + v []Survivor /* survivor list */ + n int /* number of survivors */ associations []*Association conn *net.UDPConn @@ -251,7 +247,7 @@ func (s ByDelay) Less(i, j int) bool { } func NewSystem(host, port, config, drift, socket string) *NTPalSystem { - return &NTPalSystem{ + system := &NTPalSystem{ host: host, port: port, config: config, @@ -265,6 +261,9 @@ func NewSystem(host, port, config, drift, socket string) *NTPalSystem { filtered: make(chan any), FilteredProgress: make(chan any), } + // Map the local struct values to the embedded System's + system.System.Clock = &system.Clock.Clock + return system } func (system *NTPalSystem) Start() { @@ -300,7 +299,7 @@ func (system *NTPalSystem) Start() { system.wg.Add(1) go system.setupServer() - rpcServer := &rpc.NTPalRPCServer{Socket: system.socket, System: &ntp.System{Clock: &system.Clock.Clock}, GetAssociations: system.GetAssociations} + rpcServer := &rpc.NTPalRPCServer{Socket: system.socket, System: &system.System, GetAssociations: system.GetAssociations} system.wg.Add(1) go rpcServer.Listen(&system.wg) @@ -397,7 +396,7 @@ func (system *NTPalSystem) clockAdjust() { system.Clock.lock.Lock() system.Clock.T++ - system.rootdisp += PHI + system.Rootdisp += PHI /* * Implement the phase and frequency adjustments. The gain @@ -421,7 +420,7 @@ func (system *NTPalSystem) clockAdjust() { // TODO: Might need to wrap this in system.hold == 0 ?? system.Clock.Offset -= dtemp debug("*****ADJUSTING:") - debug("TIME:", system.Clock.T, "SYS OFFSET:", system.offset, "CLOCK OFFSET:", system.Clock.Offset) + debug("TIME:", system.Clock.T, "SYS OFFSET:", system.Offset, "CLOCK OFFSET:", system.Clock.Offset) debug("FREQ: ", system.Clock.Freq*1e6, "OFFSET (dtemp):", dtemp*1e6) adjustTime(system.Clock.Freq + dtemp) @@ -445,15 +444,15 @@ func (system *NTPalSystem) clockAdjust() { if system.Clock.T%10 == 0 { sysPeerIP := "NONE" - if system.association != nil { - sysPeerIP = system.association.Srcaddr.IP.String() + if system.Association != nil { + sysPeerIP = system.Association.Srcaddr.IP.String() } info("*****REPORT:") info( "(SYSTEM):", "T:", system.t, - "OFFSET:", system.offset, - "JITTER:", system.jitter, + "OFFSET:", system.Offset, + "JITTER:", system.Jitter, "PEER:", sysPeerIP, "POLL:", system.poll, "HOLD:", system.hold, @@ -506,7 +505,7 @@ func (system *NTPalSystem) sendPoll(association *Association) { hpoll := association.Hpoll if association.hmode == ntp.BROADCAST_SERVER { association.outdate = int32(system.Clock.T) - if system.association != nil { + if system.Association != nil { system.pollPeer(association) } system.pollUpdate(association, hpoll) @@ -792,7 +791,7 @@ func (system *NTPalSystem) reply(receivePacket ntp.ReceivePacket, mode ntp.Mode) transmitPacket.Version = receivePacket.Version transmitPacket.Srcaddr = receivePacket.Dstaddr transmitPacket.Dstaddr = receivePacket.Srcaddr - transmitPacket.Leap = system.leap + transmitPacket.Leap = system.Leap transmitPacket.Mode = mode if system.stratum == MAXSTRAT { transmitPacket.Stratum = 0 @@ -801,8 +800,8 @@ func (system *NTPalSystem) reply(receivePacket ntp.ReceivePacket, mode ntp.Mode) } transmitPacket.Poll = receivePacket.Poll transmitPacket.Precision = system.precision - transmitPacket.Rootdelay = ntp.ShortEncoded(system.rootdelay * NTPShortLength) - transmitPacket.Rootdisp = ntp.ShortEncoded(system.rootdisp * NTPShortLength) + transmitPacket.Rootdelay = ntp.ShortEncoded(system.Rootdelay * NTPShortLength) + transmitPacket.Rootdisp = ntp.ShortEncoded(system.Rootdisp * NTPShortLength) transmitPacket.Refid = system.refid transmitPacket.Reftime = system.reftime transmitPacket.Org = receivePacket.Xmt @@ -835,7 +834,7 @@ func (system *NTPalSystem) pollPeer(association *Association) { */ transmitPacket.Srcaddr = association.Dstaddr transmitPacket.Dstaddr = association.Srcaddr - transmitPacket.Leap = system.leap + transmitPacket.Leap = system.Leap transmitPacket.Version = association.Version transmitPacket.Mode = association.hmode if system.stratum == MAXSTRAT { @@ -845,8 +844,8 @@ func (system *NTPalSystem) pollPeer(association *Association) { } transmitPacket.Poll = association.Hpoll transmitPacket.Precision = system.precision - transmitPacket.Rootdelay = ntp.ShortEncoded(system.rootdelay * NTPShortLength) - transmitPacket.Rootdisp = ntp.ShortEncoded(system.rootdisp * NTPShortLength) + transmitPacket.Rootdelay = ntp.ShortEncoded(system.Rootdelay * NTPShortLength) + transmitPacket.Rootdisp = ntp.ShortEncoded(system.Rootdisp * NTPShortLength) transmitPacket.Refid = system.refid transmitPacket.Reftime = system.reftime transmitPacket.Org = association.Org @@ -907,8 +906,8 @@ func (system *NTPalSystem) clear(association *Association, kiss AssociationState * the association memory as well. */ /* return resources */ - if system.association == association { - system.association = nil + if system.Association == association { + system.updateAssociation(nil) } if kiss != INIT && association.ephemeral { @@ -1047,12 +1046,12 @@ func (system *NTPalSystem) clockFilter(association *Association, offset float64, * older than the latest one, but anything goes before first * synchronized. */ - if float64(f[0].t) <= association.t && system.leap != NOSYNC { + if float64(f[0].t) <= association.t && system.Leap != NOSYNC { return } association.t = float64(f[0].t) - if association.burst == 0 || system.leap == NOSYNC { + if association.burst == 0 || system.Leap == NOSYNC { system.clockSelect() } } @@ -1070,8 +1069,8 @@ func (system *NTPalSystem) clockSelect() { * shown below, then sort the list by edge from lowest to * highest. */ - osys := system.association - system.association = nil + osys := system.Association + system.updateAssociation(nil) n := 0 system.m = []Chime{} @@ -1261,13 +1260,13 @@ func (system *NTPalSystem) clockSelect() { * survivor on the list as the new system peer. */ if osys != nil && osys.Stratum == system.v[0].association.Stratum && containsAssociation(system.v, osys) { - system.association = osys + system.updateAssociation(osys) } else { - system.association = system.v[0].association - info("NEW SYSTEM PEER picked:", system.association.Srcaddr.IP) + system.updateAssociation(system.v[0].association) + info("NEW SYSTEM PEER picked:", system.Association.Srcaddr.IP) } - system.clockUpdate(system.association) + system.clockUpdate(system.Association) } func (system *NTPalSystem) clockUpdate(association *Association) { @@ -1286,7 +1285,7 @@ func (system *NTPalSystem) clockUpdate(association *Association) { * local_clock() routine will tell us the good or bad news. */ system.clockCombine() - switch system.localClock(association, system.offset) { + switch system.localClock(association, system.Offset) { /* * The offset is too large and probably bogus. Complain to the * system log and order the operator to set the clock Manually @@ -1296,7 +1295,7 @@ func (system *NTPalSystem) clockUpdate(association *Association) { */ // TODO: Above^ case PANIC: - debug("Offset:", system.offset) + debug("Offset:", system.Offset) log.Fatal("Offset too large!") /* @@ -1317,9 +1316,9 @@ func (system *NTPalSystem) clockUpdate(association *Association) { } system.stratum = MAXSTRAT system.poll = association.minpoll - system.rootdelay = 0 - system.rootdisp = 0 - system.jitter = ntp.Log2ToDouble(system.precision) + system.Rootdelay = 0 + system.Rootdisp = 0 + system.Jitter = ntp.Log2ToDouble(system.precision) /* * The offset was less than the step threshold, which is the @@ -1332,7 +1331,7 @@ func (system *NTPalSystem) clockUpdate(association *Association) { case SLEW: info("Discipline SLEWED") // Offset and jitter already set by clockCombine() - system.leap = association.Leap + system.Leap = association.Leap system.t = uint64(association.t) system.stratum = association.Stratum + 1 if association.Stratum == 0 || association.Stratum == 16 { @@ -1341,10 +1340,10 @@ func (system *NTPalSystem) clockUpdate(association *Association) { system.refid = ipToRefID(association.Srcaddr.IP) } system.reftime = association.Reftime - system.rootdelay = association.Rootdelay + association.Delay - dtemp := math.Max(association.Rootdisp+association.Disp+system.jitter+PHI*(float64(system.Clock.T)-association.Update)+ + system.Rootdelay = association.Rootdelay + association.Delay + dtemp := math.Max(association.Rootdisp+association.Disp+system.Jitter+PHI*(float64(system.Clock.T)-association.Update)+ math.Abs(association.Offset), MINDISP) - system.rootdisp = dtemp + system.Rootdisp = dtemp fmt.Println("Root disp calc:", association.Disp, association.Rootdisp) /* * Some samples are discarded while, for instance, a direct @@ -1380,8 +1379,8 @@ func (system *NTPalSystem) clockCombine() { z += association.Offset / x w += math.Pow(association.Offset-system.v[0].association.Offset, 2) / x } - system.offset = z / y - system.jitter = math.Sqrt(w / y) + system.Offset = z / y + system.Jitter = math.Sqrt(w / y) } func (system *NTPalSystem) localClock(association *Association, offset float64) LocalClockReturnCode { @@ -1698,6 +1697,15 @@ func (system *NTPalSystem) rootDist(association *Association) float64 { association.Rootdisp + association.Disp + PHI*float64(float64(system.Clock.T)-association.Update) + association.Jitter } +func (system *NTPalSystem) updateAssociation(association *Association) { + system.Association = association + if association != nil { + system.System.Association = &association.Association + } else { + system.System.Association = nil + } +} + func (system *NTPalSystem) GetAssociations() []*ntp.Association { ntpAssociations := []*ntp.Association{} for _, association := range system.associations {