Feat: display pid and support customize unit in the table widget

This commit is contained in:
chenjiandongx 2021-11-22 23:40:16 +08:00
parent a2c0365b36
commit 9c30fc0819
9 changed files with 56 additions and 27 deletions

View File

@ -6,7 +6,7 @@
> *A modern alternative network traffic sniffer inspired by [bandwhich](https://github.com/imsnif/bandwhich)(Rust) and [nethogs](https://github.com/raboof/nethogs)(C++).* > *A modern alternative network traffic sniffer inspired by [bandwhich](https://github.com/imsnif/bandwhich)(Rust) and [nethogs](https://github.com/raboof/nethogs)(C++).*
https://user-images.githubusercontent.com/19553554/142255776-4f01f06f-af79-4fb1-820f-7a00c71416d3.mov https://user-images.githubusercontent.com/19553554/142890205-62980e37-5861-4161-9669-737317573aa1.mov
## Introduction ## Introduction

4
cli.go
View File

@ -6,7 +6,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
const version = "v0.2.0" const version = "v0.3.0"
func NewApp() *cobra.Command { func NewApp() *cobra.Command {
defaultOpts := DefaultOptions() defaultOpts := DefaultOptions()
@ -58,7 +58,7 @@ func NewApp() *cobra.Command {
app.Flags().BoolVarP(&opt.DisableDNSResolve, "no-dns-resolve", "n", defaultOpts.DisableDNSResolve, "disable the DNS resolution") app.Flags().BoolVarP(&opt.DisableDNSResolve, "no-dns-resolve", "n", defaultOpts.DisableDNSResolve, "disable the DNS resolution")
app.Flags().Int32SliceVarP(&opt.Pids, "pids", "p", defaultOpts.Pids, "pids to watch, empty stands for all pids") app.Flags().Int32SliceVarP(&opt.Pids, "pids", "p", defaultOpts.Pids, "pids to watch, empty stands for all pids")
app.Flags().IntVarP(&mode, "mode", "m", int(defaultOpts.ViewMode), "view mode of sniffer (0: bytes 1: packets 2: processes)") app.Flags().IntVarP(&mode, "mode", "m", int(defaultOpts.ViewMode), "view mode of sniffer (0: bytes 1: packets 2: processes)")
app.Flags().StringVarP(&unit, "unit", "u", defaultOpts.Unit.String(), "unit of traffic stats in processes mode, optional: B, KB, MB, GB") app.Flags().StringVarP(&unit, "unit", "u", defaultOpts.Unit.String(), "unit of traffic stats, optional: B, Kb, KB, Mb, MB, Gb, GB")
app.Flags().PrintDefaults() app.Flags().PrintDefaults()
return app return app

View File

@ -58,18 +58,17 @@ func (lc *lsofConn) GetOpenSockets(pids ...int32) (OpenSockets, error) {
if len(fields) < 10 { if len(fields) < 10 {
continue continue
} }
procName := strings.ReplaceAll(fields[0], "\\x20", " ")
procName := strings.ReplaceAll(fields[0], "\\x20", " ")
pid, _ := strconv.Atoi(fields[1])
if len(pids) > 0 { if len(pids) > 0 {
pid, err := strconv.Atoi(fields[1])
if err != nil {
continue
}
if !set[int32(pid)] { if !set[int32(pid)] {
continue continue
} }
} }
procInfo := ProcessInfo{Pid: pid, Name: procName}
switch fields[8] { switch fields[8] {
case "TCP": case "TCP":
addr := strings.Split(fields[9], "->") addr := strings.Split(fields[9], "->")
@ -84,7 +83,7 @@ func (lc *lsofConn) GetOpenSockets(pids ...int32) (OpenSockets, error) {
if err != nil { if err != nil {
continue continue
} }
sockets[LocalSocket{IP: ipport[0], Port: uint16(port), Protocol: ProtoTCP}] = procName sockets[LocalSocket{IP: ipport[0], Port: uint16(port), Protocol: ProtoTCP}] = procInfo
case "UDP": case "UDP":
ipport := strings.Split(fields[9], ":") ipport := strings.Split(fields[9], ":")
@ -96,7 +95,7 @@ func (lc *lsofConn) GetOpenSockets(pids ...int32) (OpenSockets, error) {
if err != nil { if err != nil {
continue continue
} }
sockets[LocalSocket{IP: ipport[0], Port: uint16(port), Protocol: ProtoUDP}] = procName sockets[LocalSocket{IP: ipport[0], Port: uint16(port), Protocol: ProtoUDP}] = procInfo
} }
} }

View File

@ -215,8 +215,8 @@ func (nl *netlinkConn) sockdiagSend(proto, family uint8, states uint32) (skfd in
return skfd, nil return skfd, nil
} }
func (nl *netlinkConn) sockdiagRecv(skfd, proto int, inodeMap map[uint32]string) (map[LocalSocket]string, error) { func (nl *netlinkConn) sockdiagRecv(skfd, proto int, inodeMap map[uint32]string) (OpenSockets, error) {
sockets := make(map[LocalSocket]string) sockets := make(OpenSockets)
buffer := make([]byte, os.Getpagesize()) buffer := make([]byte, os.Getpagesize())
loop: loop:
for { for {
@ -242,6 +242,11 @@ loop:
m := (*inetDiagMsg)(unsafe.Pointer(&msg.Data[0])) m := (*inetDiagMsg)(unsafe.Pointer(&msg.Data[0]))
srcIP, _ := nl.ipHex2String(m.IDiagFamily, m.ID.IdiagSrc) srcIP, _ := nl.ipHex2String(m.IDiagFamily, m.ID.IdiagSrc)
procInfo := ProcessInfo{
Pid: int(msg.Header.Pid),
Name: inodeMap[m.IDiagInode],
}
var p Protocol var p Protocol
switch proto { switch proto {
case syscall.IPPROTO_TCP: case syscall.IPPROTO_TCP:
@ -249,15 +254,15 @@ loop:
case syscall.IPPROTO_UDP: case syscall.IPPROTO_UDP:
p = ProtoUDP p = ProtoUDP
} }
sockets[LocalSocket{IP: srcIP, Port: uint16(m.ID.IdiagSport.Int()), Protocol: p}] = inodeMap[m.IDiagInode] sockets[LocalSocket{IP: srcIP, Port: uint16(m.ID.IdiagSport.Int()), Protocol: p}] = procInfo
} }
} }
return sockets, nil return sockets, nil
} }
func (nl *netlinkConn) getOpenSockets(inodeMap map[uint32]string) (map[LocalSocket]string, error) { func (nl *netlinkConn) getOpenSockets(inodeMap map[uint32]string) (OpenSockets, error) {
sockets := make(map[LocalSocket]string) sockets := make(OpenSockets)
type Req struct { type Req struct {
Protocol int Protocol int

View File

@ -28,16 +28,21 @@ func (ps *psutilConn) getOpenSockets(pids ...int32) (OpenSockets, error) {
return openSockets, nil return openSockets, nil
} }
func (ps *psutilConn) getProcName(pid int32) string { func (ps *psutilConn) getProcName(pid int32) ProcessInfo {
procInfo := ProcessInfo{Name: unknownProcessName}
proc, err := process.NewProcess(pid) proc, err := process.NewProcess(pid)
if err != nil { if err != nil {
return unknownProcessName return procInfo
} }
exe, err := proc.Exe() exe, err := proc.Exe()
if err != nil { if err != nil {
return unknownProcessName return procInfo
} }
return filepath.Base(exe)
procInfo.Pid = int(pid)
procInfo.Name = filepath.Base(exe)
return procInfo
} }
func (ps *psutilConn) getConnections(proto Protocol, openSockets OpenSockets, pids ...int32) error { func (ps *psutilConn) getConnections(proto Protocol, openSockets OpenSockets, pids ...int32) error {

12
pcap.go
View File

@ -2,6 +2,7 @@ package main
import ( import (
"errors" "errors"
"fmt"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -28,7 +29,16 @@ type Connection struct {
Remote RemoteSocket Remote RemoteSocket
} }
type OpenSockets map[LocalSocket]string type ProcessInfo struct {
Pid int
Name string
}
func (p ProcessInfo) String() string {
return fmt.Sprintf("<%d>:%s", p.Pid, p.Name)
}
type OpenSockets map[LocalSocket]ProcessInfo
type Utilization map[Connection]*ConnectionInfo type Utilization map[Connection]*ConnectionInfo
type SocketFetcher interface { type SocketFetcher interface {

View File

@ -31,7 +31,7 @@ type Options struct {
// Pids to watch in processes mode // Pids to watch in processes mode
Pids []int32 Pids []int32
// Unit of stats in processes mode, optional: B, KB, MB, GB // Unit of stats in processes mode, optional: B, Kb, KB, Mb, MB, Gb, GB
Unit Unit Unit Unit
// DisableDNSResolve decides whether if disable the DNS resolution // DisableDNSResolve decides whether if disable the DNS resolution

View File

@ -176,7 +176,7 @@ func (s *StatsManager) getProcName(openSockets OpenSockets, localSocket LocalSoc
v, ok := openSockets[cloned] v, ok := openSockets[cloned]
if ok { if ok {
return v return v.String()
} }
} }
return unknownProcessName return unknownProcessName

20
ui.go
View File

@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/chenjiandongx/termui/v3" "github.com/chenjiandongx/termui/v3"
@ -44,13 +43,16 @@ type Unit string
const ( const (
UnitB Unit = "B" UnitB Unit = "B"
UnitKB Unit = "KB" UnitKB Unit = "KB"
UnitKb Unit = "Kb"
UnitMB Unit = "MB" UnitMB Unit = "MB"
UnitMb Unit = "Mb"
UnitGB Unit = "GB" UnitGB Unit = "GB"
UnitGb Unit = "Gb"
) )
func (u Unit) Validate() error { func (u Unit) Validate() error {
switch u { switch u {
case UnitB, UnitKB, UnitMB, UnitGB: case UnitB, UnitKB, UnitKb, UnitMB, UnitMb, UnitGB, UnitGb:
return nil return nil
} }
return fmt.Errorf("invalid unit %s", u) return fmt.Errorf("invalid unit %s", u)
@ -67,10 +69,16 @@ func (u Unit) Ratio() float64 {
ratio = 1 ratio = 1
case UnitKB: case UnitKB:
ratio = 1024 ratio = 1024
case UnitKb:
ratio = 1024 / 8
case UnitMB: case UnitMB:
ratio = 1024 * 1024 ratio = 1024 * 1024
case UnitMb:
ratio = 1024 * 1024 / 8
case UnitGB: case UnitGB:
ratio = 1024 * 1024 * 1024 ratio = 1024 * 1024 * 1024
case UnitGb:
ratio = 1024 * 1024 * 1024 / 8
} }
return ratio return ratio
} }
@ -122,6 +130,7 @@ func NewUIComponent(opt Options) *UIComponent {
remoteAddrs: newTable("Remote Address"), remoteAddrs: newTable("Remote Address"),
connections: newTable("Connections"), connections: newTable("Connections"),
mode: opt.ViewMode, mode: opt.ViewMode,
unit: opt.Unit,
} }
default: default:
ui.viewer = &PlotViewer{ ui.viewer = &PlotViewer{
@ -327,6 +336,7 @@ type TableViewer struct {
grid *termui.Grid grid *termui.Grid
shiftIdx int shiftIdx int
mode ViewMode mode ViewMode
unit Unit
} }
func (tv *TableViewer) Setup() { func (tv *TableViewer) Setup() {
@ -352,7 +362,7 @@ func (tv *TableViewer) humanizeNum(n int) string {
var s string var s string
switch tv.mode { switch tv.mode {
case ModeTableBytes: case ModeTableBytes:
s = strings.ReplaceAll(humanize.IBytes(uint64(n)), " ", "") s = fmt.Sprintf("%.1f%s", float64(n)/tv.unit.Ratio(), tv.unit.String())
case ModeTablePackets: case ModeTablePackets:
s = humanize.Comma(int64(n)) s = humanize.Comma(int64(n))
} }
@ -387,7 +397,7 @@ func (tv *TableViewer) updateProcesses(snapshot *Snapshot) {
rows = append(rows, []string{r.ProcessName, strconv.Itoa(r.Data.ConnCount), up + " / " + down}) rows = append(rows, []string{r.ProcessName, strconv.Itoa(r.Data.ConnCount), up + " / " + down})
} }
header := []string{"Process", "Connections", "Up / Down"} header := []string{"<Pid>:Process", "Connections", "Up / Down"}
tv.processes.Rows = [][]string{header, make([]string, 3)} tv.processes.Rows = [][]string{header, make([]string, 3)}
tv.processes.Rows = append(tv.processes.Rows, rows...) tv.processes.Rows = append(tv.processes.Rows, rows...)
} }
@ -435,7 +445,7 @@ func (tv *TableViewer) updateConnections(snapshot *Snapshot) {
rows = append(rows, []string{conn, r.Data.ProcessName, up + " / " + down}) rows = append(rows, []string{conn, r.Data.ProcessName, up + " / " + down})
} }
header := []string{"Connections", "Process", "Up / Down"} header := []string{"Connections", "<Pid>:Process", "Up / Down"}
tv.connections.Rows = [][]string{header, make([]string, 3)} tv.connections.Rows = [][]string{header, make([]string, 3)}
tv.connections.Rows = append(tv.connections.Rows, rows...) tv.connections.Rows = append(tv.connections.Rows, rows...)
} }