From e7f88c9533fdf54c17035eb6054bb1ce644a5e84 Mon Sep 17 00:00:00 2001 From: taoky Date: Mon, 2 Sep 2024 23:46:03 +0800 Subject: [PATCH] Allow changing sort by runtime with 'S' key --- pkg/analyze/analyze.go | 8 ++++---- pkg/analyze/analyze_test.go | 2 +- pkg/analyze/sortfunc.go | 16 ++++++---------- pkg/analyze/util.go | 28 ++++++++++++++++++++++++++++ pkg/tui/interface.go | 21 ++++++++++++++++----- 5 files changed, 55 insertions(+), 20 deletions(-) diff --git a/pkg/analyze/analyze.go b/pkg/analyze/analyze.go index acb58f4..a173275 100644 --- a/pkg/analyze/analyze.go +++ b/pkg/analyze/analyze.go @@ -62,7 +62,7 @@ type AnalyzerConfig struct { Parser string RefreshSec int Server string - SortBy string + SortBy SortByFlag Threshold SizeFlag TopN int Whole bool @@ -78,7 +78,7 @@ func (c *AnalyzerConfig) InstallFlags(flags *pflag.FlagSet) { flags.StringVarP(&c.Parser, "parser", "p", c.Parser, "Log parser (nginx-combined|nginx-json|caddy-json|goaccess)") flags.IntVarP(&c.RefreshSec, "refresh", "r", c.RefreshSec, "Refresh interval in seconds") flags.StringVarP(&c.Server, "server", "s", c.Server, "Server IP to filter (nginx-json only)") - flags.StringVarP(&c.SortBy, "sort-by", "S", c.Server, "Sort result by (size|requests)") + flags.VarP(&c.SortBy, "sort-by", "S", "Sort result by (size|requests)") flags.VarP(&c.Threshold, "threshold", "t", "Threshold size for request (only requests at least this large will be counted)") flags.IntVarP(&c.TopN, "top", "n", c.TopN, "Number of top items to show") flags.BoolVarP(&c.Whole, "whole", "w", c.Whole, "Analyze whole log file and then tail it") @@ -91,7 +91,7 @@ func DefaultConfig() AnalyzerConfig { return AnalyzerConfig{ Parser: "nginx-json", RefreshSec: 5, - SortBy: "size", + SortBy: SortBySize, Threshold: SizeFlag(10e6), TopN: 10, } @@ -195,7 +195,7 @@ func (a *Analyzer) handleLine(line []byte) error { return nil } -func (a *Analyzer) PrintTopValues(displayRecord map[netip.Prefix]time.Time, sortBy string, serverFilter string) { +func (a *Analyzer) PrintTopValues(displayRecord map[netip.Prefix]time.Time, sortBy SortByFlag, serverFilter string) { activeConn := make(map[netip.Prefix]int) if !a.Config.NoNetstat { // Get active connections diff --git a/pkg/analyze/analyze_test.go b/pkg/analyze/analyze_test.go index 4cd74cc..22015ba 100644 --- a/pkg/analyze/analyze_test.go +++ b/pkg/analyze/analyze_test.go @@ -34,7 +34,7 @@ func benchmarkAnalyzeLoop(b *testing.B, parserStr string) { } a.RunLoop(t) - a.PrintTopValues(nil, "size", "") + a.PrintTopValues(nil, SortBySize, "") } func BenchmarkAnalyzeLoopNgxJSON(b *testing.B) { diff --git a/pkg/analyze/sortfunc.go b/pkg/analyze/sortfunc.go index dcef64d..830d138 100644 --- a/pkg/analyze/sortfunc.go +++ b/pkg/analyze/sortfunc.go @@ -6,24 +6,20 @@ import ( type SortFunc func(l, r StatKey) int -var sortFuncs = map[string]func(a map[StatKey]IPStats) SortFunc{ - "size": func(i map[StatKey]IPStats) SortFunc { +var sortFuncs = map[SortByFlag]func(a map[StatKey]IPStats) SortFunc{ + SortBySize: func(i map[StatKey]IPStats) SortFunc { return func(l, r StatKey) int { return int(i[r].Size - i[l].Size) } }, - "requests": func(i map[StatKey]IPStats) SortFunc { + SortByRequests: func(i map[StatKey]IPStats) SortFunc { return func(l, r StatKey) int { return int(i[r].Requests - i[l].Requests) } }, } -func init() { - sortFuncs["reqs"] = sortFuncs["requests"] -} - -func GetSortFunc(name string, i map[StatKey]IPStats) SortFunc { +func GetSortFunc(name SortByFlag, i map[StatKey]IPStats) SortFunc { fn, ok := sortFuncs[name] if !ok { return nil @@ -31,8 +27,8 @@ func GetSortFunc(name string, i map[StatKey]IPStats) SortFunc { return fn(i) } -func ListSortFuncs() []string { - ret := make([]string, 0, len(sortFuncs)) +func ListSortFuncs() []SortByFlag { + ret := make([]SortByFlag, 0, len(sortFuncs)) for key := range sortFuncs { ret = append(ret, key) } diff --git a/pkg/analyze/util.go b/pkg/analyze/util.go index f7629ea..c855a7a 100644 --- a/pkg/analyze/util.go +++ b/pkg/analyze/util.go @@ -1,6 +1,7 @@ package analyze import ( + "errors" "net/netip" "strconv" @@ -33,6 +34,33 @@ func (s SizeFlag) Type() string { return "size" } +type SortByFlag string + +const ( + SortBySize SortByFlag = "size" + SortByRequests SortByFlag = "requests" +) + +func (s SortByFlag) String() string { + return string(s) +} + +func (s *SortByFlag) Set(value string) error { + switch value { + case "size": + *s = SortBySize + case "requests", "reqs": + *s = SortByRequests + default: + return errors.New(`must be one of "size" or "requests"`) + } + return nil +} + +func (s SortByFlag) Type() string { + return "size|requests" +} + func IPPrefix(ip netip.Addr) netip.Prefix { var clientPrefix netip.Prefix if ip.Is4() { diff --git a/pkg/tui/interface.go b/pkg/tui/interface.go index fba8de3..073b7a5 100644 --- a/pkg/tui/interface.go +++ b/pkg/tui/interface.go @@ -18,7 +18,8 @@ const ( const helpMsg = `Available shortcuts: t/T: print total size aggregated by server -s/S: get user input for server filtering +s: set server filtering +S: change sort by ?: help` type Tui struct { @@ -27,6 +28,7 @@ type Tui struct { displayRecord map[netip.Prefix]time.Time serverFilter string mode ShowMode + sortBy analyze.SortByFlag ticker *time.Ticker refreshChan chan struct{} inputChan chan byte @@ -38,6 +40,7 @@ func New(analyzer *analyze.Analyzer) *Tui { analyzer: analyzer, displayRecord: make(map[netip.Prefix]time.Time), mode: TopValues, + sortBy: analyzer.Config.SortBy, refreshChan: make(chan struct{}), inputChan: make(chan byte), } @@ -54,7 +57,7 @@ func (t *Tui) Run() { t.handleInput(k) case <-t.refreshChan: if t.mode == TopValues { - a.PrintTopValues(t.displayRecord, "size", t.serverFilter) + a.PrintTopValues(t.displayRecord, t.sortBy, t.serverFilter) } else { a.PrintTotal() } @@ -65,8 +68,16 @@ func (t *Tui) Run() { func (t *Tui) handleInput(key byte) { switch key { - case 'S', 's': - t.handleS() + case 's': + t.handles() + case 'S': + if t.sortBy == analyze.SortBySize { + t.sortBy = analyze.SortByRequests + fmt.Println("Switched to sort by requests") + } else { + t.sortBy = analyze.SortBySize + fmt.Println("Switched to sort by size") + } case 'T', 't': if t.mode == TopValues { t.mode = Total @@ -82,7 +93,7 @@ func (t *Tui) handleInput(key byte) { go t.waitForOneByte() } -func (t *Tui) handleS() { +func (t *Tui) handles() { t.noPrint.Store(true) defer t.noPrint.Store(false)