ethr/serverui.go
Pankaj Garg 0c9674d15d
Misc/codecleanup (#29)
* Intermediate changes for Connected UDP.

* Add more changes to support connected UDP.

There is an issue with this approach, as multiple sockets are not able
to listen on connected UDP. We need to add SO_REUSEPORT to make this a
viable solution. This would be done in a later commit but before this
change is merged into master.

* Minor code cleanup.
2018-12-10 08:06:37 -08:00

447 lines
11 KiB
Go

//-----------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license.
// See LICENSE.txt file in the project root for full license information.
//-----------------------------------------------------------------------------
package main
import (
"errors"
"fmt"
"os"
"sync"
"sync/atomic"
"time"
tm "github.com/nsf/termbox-go"
)
type ethrTestResultAggregate struct {
bw, cps, pps uint64
cbw, ccps, cpps uint64
}
var gAggregateTestResults = make(map[EthrProtocol]*ethrTestResultAggregate)
//
// Initialization functions.
//
func initServerUi(showUi bool) {
gAggregateTestResults[Tcp] = &ethrTestResultAggregate{}
gAggregateTestResults[Udp] = &ethrTestResultAggregate{}
gAggregateTestResults[Http] = &ethrTestResultAggregate{}
gAggregateTestResults[Https] = &ethrTestResultAggregate{}
gAggregateTestResults[Icmp] = &ethrTestResultAggregate{}
if !showUi || !initServerTui() {
initServerCli()
}
}
//
// Text based UI
//
type serverTui struct {
h, w int
resX, resY, resW int
latX, latY, latW int
topVSplitX, topVSplitY, topVSplitH int
statX, statY, statW int
msgX, msgY, msgW int
botVSplitX, botVSplitY, botVSplitH int
errX, errY, errW int
res table
results [][]string
resultHdr []string
msg table
msgRing []string
err table
errRing []string
ringLock sync.RWMutex
}
func initServerTui() bool {
err := initServerTuiInternal()
if err != nil {
fmt.Println("Error: Failed to initialize UI.", err)
fmt.Println("Using command line view instead of UI")
return false
}
return true
}
func initServerTuiInternal() error {
err := tm.Init()
if err != nil {
return err
}
w, h := tm.Size()
if h < 40 || w < 80 {
tm.Close()
s := fmt.Sprintf("Terminal too small (%dwx%dh), must be at least 40hx80w", w, h)
return errors.New(s)
}
tm.SetInputMode(tm.InputEsc | tm.InputMouse)
tm.Clear(tm.ColorDefault, tm.ColorDefault)
tm.Sync()
tm.Flush()
hideCursor()
blockWindowResize()
tui := &serverTui{}
botScnH := 8
statScnW := 26
tui.h = h
tui.w = w
tui.resX = 0
tui.resY = 2
tui.resW = w - statScnW
tui.latX = 0
tui.latY = h - botScnH
tui.latW = w
tui.topVSplitX = tui.resW
tui.topVSplitY = 1
tui.topVSplitH = h - botScnH
tui.statX = tui.topVSplitX + 1
tui.statY = 2
tui.statW = statScnW
tui.msgX = 0
tui.msgY = h - botScnH + 1
tui.msgW = (w+1)/2 + 1
tui.botVSplitX = tui.msgW
tui.botVSplitY = h - botScnH
tui.botVSplitH = botScnH
tui.errX = tui.botVSplitX + 1
tui.errY = h - botScnH + 1
tui.errW = w - tui.msgW - 1
tui.res = table{6, []int{13, 5, 7, 7, 7, 8}, 0, 2, 0, justifyRight, noBorder}
tui.results = make([][]string, 0)
tui.msg = table{1, []int{tui.msgW}, tui.msgX, tui.msgY, 0, justifyLeft, noBorder}
tui.msgRing = make([]string, botScnH-1)
tui.err = table{1, []int{tui.errW}, tui.errX, tui.errY, 0, justifyLeft, noBorder}
tui.errRing = make([]string, botScnH-1)
ui = tui
go func() {
for {
switch ev := tm.PollEvent(); ev.Type {
case tm.EventKey:
if ev.Key == tm.KeyEsc || ev.Key == tm.KeyCtrlC {
finiServer()
os.Exit(0)
}
case tm.EventResize:
}
}
}()
return nil
}
func (u *serverTui) fini() {
tm.Close()
}
func (u *serverTui) printMsg(format string, a ...interface{}) {
s := fmt.Sprintf(format, a...)
logMsg(s)
ss := splitString(s, u.msgW)
u.ringLock.Lock()
u.msgRing = u.msgRing[len(ss):]
u.msgRing = append(u.msgRing, ss...)
u.ringLock.Unlock()
}
func (u *serverTui) printErr(format string, a ...interface{}) {
s := fmt.Sprintf(format, a...)
logErr(s)
ss := splitString(s, u.errW)
u.ringLock.Lock()
u.errRing = u.errRing[len(ss):]
u.errRing = append(u.errRing, ss...)
u.ringLock.Unlock()
}
func (u *serverTui) printDbg(format string, a ...interface{}) {
if logDebug {
s := fmt.Sprintf(format, a...)
logDbg(s)
ss := splitString(s, u.errW)
u.ringLock.Lock()
u.errRing = u.errRing[len(ss):]
u.errRing = append(u.errRing, ss...)
u.ringLock.Unlock()
}
}
func (u *serverTui) emitTestResultBegin() {
u.results = nil
}
func (u *serverTui) emitTestResult(s *ethrSession, proto EthrProtocol) {
str := getTestResults(s, proto)
if len(str) > 0 {
ui.printTestResults(str)
}
}
func (u *serverTui) printTestResults(s []string) {
// Log before truncation of remote address.
logResults(s)
s[0] = truncateString(s[0], 13)
u.results = append(u.results, s)
}
func (u *serverTui) emitTestResultEnd() {
emitAggregateResults()
}
func (u *serverTui) emitTestHdr() {
s := []string{"RemoteAddress", "Proto", "Bits/s", "Conn/s", "Pkts/s", "Latency"}
u.resultHdr = s
}
func (u *serverTui) emitLatencyHdr() {
}
func (u *serverTui) emitLatencyResults(remote, proto string, avg, min, max, p50, p90, p95, p99, p999, p9999 time.Duration) {
logLatency(remote, proto, avg, min, max, p50, p90, p95, p99, p999, p9999)
}
func (u *serverTui) paint() {
tm.Clear(tm.ColorDefault, tm.ColorDefault)
defer tm.Flush()
printCenterText(0, 0, u.w, "Ethr v0.1", tm.ColorBlack, tm.ColorWhite)
printHLineText(u.resX, u.resY-1, u.resW, "Test Results")
printHLineText(u.statX, u.statY-1, u.statW, "Statistics")
printVLine(u.topVSplitX, u.topVSplitY, u.topVSplitH)
printHLineText(u.msgX, u.msgY-1, u.msgW, "Messages")
printHLineText(u.errX, u.errY-1, u.errW, "Errors")
u.ringLock.Lock()
u.msg.cr = 0
for _, s := range u.msgRing {
u.msg.addTblRow([]string{s})
}
u.err.cr = 0
for _, s := range u.errRing {
u.err.addTblRow([]string{s})
}
u.ringLock.Unlock()
printVLine(u.botVSplitX, u.botVSplitY, u.botVSplitH)
u.res.cr = 0
if u.resultHdr != nil {
u.res.addTblHdr()
u.res.addTblRow(u.resultHdr)
u.res.addTblSpr()
}
for _, s := range u.results {
u.res.addTblRow(s)
u.res.addTblSpr()
}
if len(gPrevNetStats.netDevStats) == 0 {
return
}
x := u.statX
w := u.statW
y := u.statY
for _, ns := range gCurNetStats.netDevStats {
nsDiff := getNetDevStatDiff(ns, gPrevNetStats)
// TODO: Log the network adapter stats in file as well.
printText(x, y, w, fmt.Sprintf("if: %s", ns.interfaceName), tm.ColorWhite, tm.ColorBlack)
y++
printText(x, y, w, fmt.Sprintf("Tx %sbps", bytesToRate(nsDiff.txBytes)), tm.ColorWhite, tm.ColorBlack)
bw := nsDiff.txBytes * 8
printUsageBar(x+14, y, 10, bw, KILO, tm.ColorYellow)
y++
printText(x, y, w, fmt.Sprintf("Rx %sbps", bytesToRate(nsDiff.rxBytes)), tm.ColorWhite, tm.ColorBlack)
bw = nsDiff.rxBytes * 8
printUsageBar(x+14, y, 10, bw, KILO, tm.ColorGreen)
y++
printText(x, y, w, fmt.Sprintf("Tx %spps", numberToUnit(nsDiff.txPkts)), tm.ColorWhite, tm.ColorBlack)
printUsageBar(x+14, y, 10, nsDiff.txPkts, 10, tm.ColorWhite)
y++
printText(x, y, w, fmt.Sprintf("Rx %spps", numberToUnit(nsDiff.rxPkts)), tm.ColorWhite, tm.ColorBlack)
printUsageBar(x+14, y, 10, nsDiff.rxPkts, 10, tm.ColorCyan)
y++
printText(x, y, w, "-------------------------", tm.ColorDefault, tm.ColorDefault)
y++
}
printText(x, y, w,
fmt.Sprintf("Tcp Retrans: %s", numberToUnit(gCurNetStats.tcpStats.segRetrans-gPrevNetStats.tcpStats.segRetrans)),
tm.ColorDefault, tm.ColorDefault)
}
var gPrevNetStats ethrNetStat
var gCurNetStats ethrNetStat
func (u *serverTui) emitStats(netStats ethrNetStat) {
gPrevNetStats = gCurNetStats
gCurNetStats = netStats
}
//
// Simple command window based output
//
type serverCli struct {
}
func initServerCli() {
cli := &serverCli{}
ui = cli
}
func (u *serverCli) fini() {
}
func (u *serverCli) printMsg(format string, a ...interface{}) {
s := fmt.Sprintf(format, a...)
fmt.Println(s)
logMsg(s)
}
func (u *serverCli) printDbg(format string, a ...interface{}) {
if logDebug {
s := fmt.Sprintf(format, a...)
fmt.Println(s)
logDbg(s)
}
}
func (u *serverCli) printErr(format string, a ...interface{}) {
s := fmt.Sprintf(format, a...)
fmt.Println(s)
logErr(s)
}
func (u *serverCli) paint() {
}
func (u *serverCli) emitTestResultBegin() {
gSessionLock.RLock()
l := len(gSessionKeys)
gSessionLock.RUnlock()
if l > 1 {
fmt.Println("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -")
}
}
func (u *serverCli) emitTestResult(s *ethrSession, proto EthrProtocol) {
str := getTestResults(s, proto)
if len(str) > 0 {
ui.printTestResults(str)
}
}
func (u *serverCli) emitTestResultEnd() {
emitAggregateResults()
}
func (u *serverCli) emitTestHdr() {
s := []string{"RemoteAddress", "Proto", "Bits/s", "Conn/s", "Pkt/s", "Latency"}
fmt.Println("-----------------------------------------------------------")
fmt.Printf("[%13s] %5s %7s %7s %7s %8s\n", s[0], s[1], s[2], s[3], s[4], s[5])
}
func (u *serverCli) emitLatencyHdr() {
}
func (u *serverCli) emitLatencyResults(remote, proto string, avg, min, max, p50, p90, p95, p99, p999, p9999 time.Duration) {
logLatency(remote, proto, avg, min, max, p50, p90, p95, p99, p999, p9999)
}
func (u *serverCli) emitStats(netStats ethrNetStat) {
}
func (u *serverCli) printTestResults(s []string) {
logResults(s)
fmt.Printf("[%13s] %5s %7s %7s %7s %8s\n", truncateString(s[0], 13),
s[1], s[2], s[3], s[4], s[5])
}
func emitAggregateResults() {
var protoList = []EthrProtocol{Tcp, Udp, Http, Https, Icmp}
for _, proto := range protoList {
emitAggregate(proto)
}
}
func emitAggregate(proto EthrProtocol) {
str := []string{}
aggTestResult, _ := gAggregateTestResults[proto]
if aggTestResult.cbw > 1 || aggTestResult.ccps > 1 || aggTestResult.cpps > 1 {
str = []string{"[SUM]", protoToString(proto),
bytesToRate(aggTestResult.bw),
cpsToString(aggTestResult.cps),
ppsToString(aggTestResult.pps),
""}
}
aggTestResult.bw = 0
aggTestResult.cps = 0
aggTestResult.pps = 0
aggTestResult.cbw = 0
aggTestResult.ccps = 0
aggTestResult.cpps = 0
if len(str) > 0 {
ui.printTestResults(str)
}
}
func getTestResults(s *ethrSession, proto EthrProtocol) []string {
var bwTestOn, cpsTestOn, ppsTestOn, latTestOn bool
var bw, cps, pps, latency uint64
aggTestResult, _ := gAggregateTestResults[proto]
test, found := s.tests[EthrTestId{proto, Bandwidth}]
if found && test.isActive {
bwTestOn = true
bw = atomic.SwapUint64(&test.testResult.data, 0)
aggTestResult.bw += bw
aggTestResult.cbw++
}
test, found = s.tests[EthrTestId{proto, Cps}]
if found && test.isActive {
cpsTestOn = true
cps = atomic.SwapUint64(&test.testResult.data, 0)
aggTestResult.cps += cps
aggTestResult.ccps++
}
test, found = s.tests[EthrTestId{proto, Pps}]
if found && test.isActive {
ppsTestOn = true
pps = atomic.SwapUint64(&test.testResult.data, 0)
aggTestResult.pps += pps
aggTestResult.cpps++
}
test, found = s.tests[EthrTestId{proto, Latency}]
if found && test.isActive {
latTestOn = true
latency = atomic.LoadUint64(&test.testResult.data)
}
if bwTestOn || cpsTestOn || ppsTestOn || latTestOn {
var bwStr, cpsStr, ppsStr, latStr string
if bwTestOn {
bwStr = bytesToRate(bw)
}
if cpsTestOn {
cpsStr = cpsToString(cps)
}
if ppsTestOn {
ppsStr = ppsToString(pps)
}
if latTestOn {
latStr = durationToString(time.Duration(latency))
}
str := []string{s.remoteAddr, protoToString(proto),
bwStr, cpsStr, ppsStr, latStr}
return str
}
return []string{}
}