blob: 11952159e65d4bdc1f9d88a6fdf70ad32e5b6fed [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package main
import (
"flag"
"fmt"
"os"
"strings"
"time"
"app/context"
"fidl/fuchsia/netstack"
"github.com/google/netstack/tcpip"
)
type netstackClientApp struct {
ctx *context.Context
netstack *netstack.NetstackInterface
}
func (a *netstackClientApp) start(nicName string) {
ifaces, err := a.netstack.GetInterfaces()
if err != nil {
fmt.Printf("Failed to fetch interfaces: %v\n", err)
return
}
for _, iface := range ifaces {
// TODO (porce): Both Watcher(), Golang map[] do no sort the network interfaces.
// Sort them.
if nicName != "" && nicName != iface.Name {
continue
}
nicid := iface.Id
stats, err := a.netstack.GetStats(uint32(nicid))
if err != nil {
fmt.Printf("Failed to get stats for interface nicid %d name %s: %v\n", nicid, iface.Name, err)
// TODO (porce): A single failure leads to
// ErrPeerClosed: zx.Channel.Read and
// ErrPeerClosed: bindings.Router.AcceptWithResponse.
// Think of how a particular interface error will be handled.
continue
}
printIfStats(iface, stats)
fmt.Printf("\n")
}
}
func formattedSince(since time.Time) (sinceStr string) {
// Returns human-friendly duration string in Sec: eg. "4m10s".
s := time.Since(since)
s -= s % time.Second // Truncate to sec.
return s.String()
}
func formattedSinceInt(since int64) (sinceStr string) {
return formattedSince(time.Unix(since, 0))
}
func printIfStats(ni netstack.NetInterface, stats netstack.NetInterfaceStats) { // experiment
// TODO (porce): if the interface is down, up_howlong := "-"
datetime := time.Unix(stats.UpSince, 0)
fmt.Printf("\n%s:\n", ni.Name)
fmt.Printf("Up since: %s (%s ago)\n\n", datetime, formattedSinceInt(stats.UpSince))
alignTitle := "%-14s %-6s %-12s %-10s\n" // Bucket, Unit, Rx stat, Tx stat
fmt.Printf(alignTitle, "Bucket", "Unit", "Rx", "Tx")
fmt.Printf(alignTitle, strings.Repeat("-", 14), strings.Repeat("-", 6), strings.Repeat("-", 12), strings.Repeat("-", 12))
align := "%14s %6s %12d %12d\n" // Bucket, Unit, Rx stat, Tx stat
const C = "[#]" // Counts
const B = "[B]" // Bytes
fmt.Printf(align, "Total", B, stats.Rx.BytesTotal, stats.Tx.BytesTotal)
fmt.Printf(align, "Total", C, stats.Rx.PktsTotal, stats.Tx.PktsTotal)
fmt.Printf(align, "EchoReQ", C, stats.Rx.PktsEchoReq, stats.Tx.PktsEchoReq)
fmt.Printf(align, "EchoRep", C, stats.Rx.PktsEchoRep, stats.Tx.PktsEchoRep)
fmt.Printf(align, "v6 EchoReQ", C, stats.Rx.PktsEchoReqV6, stats.Tx.PktsEchoReqV6)
fmt.Printf(align, "v6 EchoRep", C, stats.Rx.PktsEchoRepV6, stats.Tx.PktsEchoRepV6)
fmt.Printf("\n")
}
func main() {
flag.Usage = usage
var watch bool
var sec uint
var period uint // sec
flag.BoolVar(&watch, "watch", false, "watch a NIC every period")
flag.UintVar(&sec, "sec", 10, "how many sec to watch. 0 for forever")
flag.UintVar(&period, "period", 1, "stat update period in sec")
flag.Parse()
a := &netstackClientApp{ctx: context.CreateFromStartupInfo()}
nicName := ""
if len(flag.Args()) != 0 {
nicName = flag.Args()[0]
}
if watch == true && nicName == "" {
// Monitoring requires a single NIC
flag.Usage()
return
}
req, pxy, err := netstack.NewNetstackInterfaceRequest()
if err != nil {
panic(err.Error())
}
a.netstack = pxy
a.ctx.ConnectToEnvService(req)
defer a.netstack.Close()
if watch == false { // This trick makes it possible to the check the presence of the flag, not only its value.
a.start(nicName)
return
}
// Watch the interface every second.
isForever := (sec == 0)
err = a.watchIface(nicName, sec, isForever, period)
if err != nil {
fmt.Printf("%v\n", err)
}
}
func (a *netstackClientApp) getNICID(nicName string) (nicid tcpip.NICID, err error) {
ifaces, err := a.netstack.GetInterfaces()
if err != nil {
return tcpip.NICID(0), err
}
for _, iface := range ifaces {
if nicName == iface.Name {
return tcpip.NICID(iface.Id), nil
}
}
return tcpip.NICID(0), fmt.Errorf("No NIC for such name: %s", nicName)
}
func (a *netstackClientApp) watchIface(nicName string, sec uint, isForever bool, period uint) (err error) {
if nicName == "" {
return fmt.Errorf("Please specify the NIC name")
}
nicid, err := a.getNICID(nicName)
if err != nil {
return err
}
statsPrev := netstack.NetInterfaceStats{}
timeRx := time.Time{}
timeTx := time.Time{}
errDuration := 0 // sec for prolonged error in querying stats.
for isForever || sec > 0 {
stats, err := a.netstack.GetStats(uint32(nicid))
now := time.Now()
nowStr := now.Truncate(time.Second).String()
if err != nil {
errDuration += 1
nowStr += fmt.Sprintf(" : Error for %d sec (%s)", errDuration, err.Error())
goto sleep
}
errDuration = 0
if statsPrev == stats {
goto sleep
}
if statsPrev.Rx.BytesTotal != stats.Rx.BytesTotal {
timeRx = now
}
if statsPrev.Tx.BytesTotal != stats.Tx.BytesTotal {
timeTx = now
}
statsPrev = stats
sleep:
statsOneliner(nicName, stats, formattedSince(timeRx), formattedSince(timeTx), nowStr)
time.Sleep(time.Duration(period) * time.Second)
sec -= period
}
fmt.Printf("\n\n")
return nil
}
func statsOneliner(nicName string, stats netstack.NetInterfaceStats, timeRx string, timeTx string, nowStr string) {
// ANSI escape sequence: Erases everything written on line before this
// and move the cursor to the beginning of the line.
// fmt.Printf("\033[2K\r") // .. does not work well.
fmt.Printf("\r%s: Rx [%5s ago] %10d pkts Tx [%5s ago] %10d pkts %s",
nicName, timeRx, stats.Rx.PktsTotal, timeTx, stats.Tx.PktsTotal, nowStr)
}
func usage() {
fmt.Printf("Usage: %s [OPTIONS] [NIC_NAME]\n", os.Args[0])
flag.PrintDefaults()
}