summaryrefslogtreecommitdiff
path: root/internal/tui/eventstream
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-08 20:10:20 +0200
committerPaul Buetow <paul@buetow.org>2026-03-08 20:10:20 +0200
commit21aa0cd0f96087fa040750643109c496e7a1b3ee (patch)
treeedb29ed949cf638d5c2a759dd3bf8840fed45922 /internal/tui/eventstream
parent7ad3bb96f4d07bdd8b20b561257a84c7f18c3829 (diff)
task 366: extract shared global filter types
Diffstat (limited to 'internal/tui/eventstream')
-rw-r--r--internal/tui/eventstream/filter.go230
-rw-r--r--internal/tui/eventstream/model.go37
-rw-r--r--internal/tui/eventstream/streamevent.go44
3 files changed, 59 insertions, 252 deletions
diff --git a/internal/tui/eventstream/filter.go b/internal/tui/eventstream/filter.go
index 4e0daf7..61d8c33 100644
--- a/internal/tui/eventstream/filter.go
+++ b/internal/tui/eventstream/filter.go
@@ -1,227 +1,21 @@
package eventstream
-import (
- "fmt"
- "strconv"
- "strings"
- "time"
-)
+import "ior/internal/globalfilter"
-type CompareOp int
+type CompareOp = globalfilter.CompareOp
+type NumericFilter = globalfilter.NumericFilter
+type StringFilter = globalfilter.StringFilter
+type Filter = globalfilter.Filter
const (
- OpEq CompareOp = iota
- OpNeq
- OpGt
- OpGte
- OpLt
- OpLte
+ OpEq = globalfilter.OpEq
+ OpNeq = globalfilter.OpNeq
+ OpGt = globalfilter.OpGt
+ OpGte = globalfilter.OpGte
+ OpLt = globalfilter.OpLt
+ OpLte = globalfilter.OpLte
)
-type NumericFilter struct {
- Op CompareOp
- Value int64
-}
-
-type StringFilter struct {
- Pattern string
-}
-
-type Filter struct {
- Syscall *StringFilter
- Comm *StringFilter
- File *StringFilter
- PID *NumericFilter
- TID *NumericFilter
- FD *NumericFilter
- LatencyNs *NumericFilter
- GapNs *NumericFilter
- Bytes *NumericFilter
- RetVal *NumericFilter
- ErrorsOnly bool
-}
-
-func (f Filter) Matches(ev *StreamEvent) bool {
- if ev == nil {
- return false
- }
- if f.ErrorsOnly && !ev.IsError {
- return false
- }
- if !matchString(f.Syscall, ev.Syscall) {
- return false
- }
- if !matchString(f.Comm, ev.Comm) {
- return false
- }
- if !matchString(f.File, ev.FileName) {
- return false
- }
- if !matchNumeric(f.PID, int64(ev.PID)) {
- return false
- }
- if !matchNumeric(f.TID, int64(ev.TID)) {
- return false
- }
- if !matchNumeric(f.FD, int64(ev.FD)) {
- return false
- }
- if !matchNumeric(f.LatencyNs, int64(ev.DurationNs)) {
- return false
- }
- if !matchNumeric(f.GapNs, int64(ev.GapNs)) {
- return false
- }
- if !matchNumeric(f.Bytes, int64(ev.Bytes)) {
- return false
- }
- if !matchNumeric(f.RetVal, ev.RetVal) {
- return false
- }
- return true
-}
-
-func (f Filter) IsActive() bool {
- if f.ErrorsOnly {
- return true
- }
- for _, sf := range []*StringFilter{f.Syscall, f.Comm, f.File} {
- if sf != nil && strings.TrimSpace(sf.Pattern) != "" {
- return true
- }
- }
- for _, nf := range []*NumericFilter{f.PID, f.TID, f.FD, f.LatencyNs, f.GapNs, f.Bytes, f.RetVal} {
- if nf != nil {
- return true
- }
- }
- return false
-}
-
-func (f Filter) Summary() string {
- parts := make([]string, 0, 10)
- if f.ErrorsOnly {
- parts = append(parts, "errors")
- }
- parts = appendStringSummary(parts, "syscall", f.Syscall)
- parts = appendStringSummary(parts, "comm", f.Comm)
- parts = appendStringSummary(parts, "file", f.File)
- parts = appendNumericSummary(parts, "pid", f.PID, false)
- parts = appendNumericSummary(parts, "tid", f.TID, false)
- parts = appendNumericSummary(parts, "fd", f.FD, false)
- parts = appendNumericSummary(parts, "latency", f.LatencyNs, true)
- parts = appendNumericSummary(parts, "gap", f.GapNs, true)
- parts = appendNumericSummary(parts, "bytes", f.Bytes, false)
- parts = appendNumericSummary(parts, "ret", f.RetVal, false)
- if len(parts) == 0 {
- return "all"
- }
- return strings.Join(parts, " ")
-}
-
func ParseDurationNs(input string) (int64, error) {
- s := strings.TrimSpace(strings.ToLower(input))
- if s == "" {
- return 0, fmt.Errorf("empty duration")
- }
- s = strings.ReplaceAll(s, "µs", "us")
- s = strings.ReplaceAll(s, "μs", "us")
- if onlyDigits(s) || strings.HasPrefix(s, "-") && onlyDigits(s[1:]) {
- v, err := strconv.ParseInt(s, 10, 64)
- if err != nil {
- return 0, err
- }
- return v, nil
- }
- d, err := time.ParseDuration(s)
- if err != nil {
- return 0, err
- }
- return d.Nanoseconds(), nil
-}
-
-func appendStringSummary(parts []string, name string, sf *StringFilter) []string {
- if sf == nil {
- return parts
- }
- pattern := strings.TrimSpace(sf.Pattern)
- if pattern == "" {
- return parts
- }
- return append(parts, fmt.Sprintf("%s~%s", name, pattern))
-}
-
-func appendNumericSummary(parts []string, name string, nf *NumericFilter, duration bool) []string {
- if nf == nil {
- return parts
- }
- value := strconv.FormatInt(nf.Value, 10)
- if duration {
- value = time.Duration(nf.Value).String()
- }
- return append(parts, fmt.Sprintf("%s%s%s", name, compareOpSymbol(nf.Op), value))
-}
-
-func matchString(sf *StringFilter, value string) bool {
- if sf == nil {
- return true
- }
- pattern := strings.ToLower(strings.TrimSpace(sf.Pattern))
- if pattern == "" {
- return true
- }
- return strings.Contains(strings.ToLower(value), pattern)
-}
-
-func matchNumeric(nf *NumericFilter, value int64) bool {
- if nf == nil {
- return true
- }
- switch nf.Op {
- case OpEq:
- return value == nf.Value
- case OpNeq:
- return value != nf.Value
- case OpGt:
- return value > nf.Value
- case OpGte:
- return value >= nf.Value
- case OpLt:
- return value < nf.Value
- case OpLte:
- return value <= nf.Value
- default:
- return false
- }
-}
-
-func compareOpSymbol(op CompareOp) string {
- switch op {
- case OpEq:
- return "="
- case OpNeq:
- return "!="
- case OpGt:
- return ">"
- case OpGte:
- return ">="
- case OpLt:
- return "<"
- case OpLte:
- return "<="
- default:
- return "?"
- }
-}
-
-func onlyDigits(s string) bool {
- if s == "" {
- return false
- }
- for _, ch := range s {
- if ch < '0' || ch > '9' {
- return false
- }
- }
- return true
+ return globalfilter.ParseDurationNs(input)
}
diff --git a/internal/tui/eventstream/model.go b/internal/tui/eventstream/model.go
index 12aff4d..ee65793 100644
--- a/internal/tui/eventstream/model.go
+++ b/internal/tui/eventstream/model.go
@@ -751,7 +751,7 @@ func (m *Model) applyFilterFromSelectedCell() bool {
}
ev := m.filtered[m.selectedIdx]
targetSeq := ev.Seq
- next := cloneFilter(m.filter)
+ next := m.filter.Clone()
action := ""
switch m.selectedCol {
@@ -789,7 +789,7 @@ func (m *Model) applyFilterFromSelectedCell() bool {
return false
}
- m.filterStack = append(m.filterStack, cloneFilter(m.filter))
+ m.filterStack = append(m.filterStack, m.filter.Clone())
m.filterActionStack = append(m.filterActionStack, action)
m.filter = next
m.applyFilter()
@@ -807,7 +807,7 @@ func (m *Model) popFilter() bool {
if len(m.filterActionStack) > 0 {
m.filterActionStack = m.filterActionStack[:len(m.filterActionStack)-1]
}
- m.filter = cloneFilter(last)
+ m.filter = last.Clone()
m.applyFilter()
m.restoreSelectionBySeq(targetSeq)
return true
@@ -833,37 +833,6 @@ func (m *Model) restoreSelectionBySeq(seq uint64) {
}
}
-func cloneFilter(in Filter) Filter {
- out := in
- out.Syscall = cloneStringFilter(in.Syscall)
- out.Comm = cloneStringFilter(in.Comm)
- out.File = cloneStringFilter(in.File)
- out.PID = cloneNumericFilter(in.PID)
- out.TID = cloneNumericFilter(in.TID)
- out.FD = cloneNumericFilter(in.FD)
- out.LatencyNs = cloneNumericFilter(in.LatencyNs)
- out.GapNs = cloneNumericFilter(in.GapNs)
- out.Bytes = cloneNumericFilter(in.Bytes)
- out.RetVal = cloneNumericFilter(in.RetVal)
- return out
-}
-
-func cloneStringFilter(in *StringFilter) *StringFilter {
- if in == nil {
- return nil
- }
- out := *in
- return &out
-}
-
-func cloneNumericFilter(in *NumericFilter) *NumericFilter {
- if in == nil {
- return nil
- }
- out := *in
- return &out
-}
-
func (m *Model) clampSelection() {
if len(m.filtered) == 0 {
m.selectedIdx = -1
diff --git a/internal/tui/eventstream/streamevent.go b/internal/tui/eventstream/streamevent.go
index 5f1e27f..9238a84 100644
--- a/internal/tui/eventstream/streamevent.go
+++ b/internal/tui/eventstream/streamevent.go
@@ -23,6 +23,50 @@ type StreamEvent struct {
FD int32
}
+func (e StreamEvent) SyscallValue() string {
+ return e.Syscall
+}
+
+func (e StreamEvent) CommValue() string {
+ return e.Comm
+}
+
+func (e StreamEvent) FileValue() string {
+ return e.FileName
+}
+
+func (e StreamEvent) PIDValue() uint32 {
+ return e.PID
+}
+
+func (e StreamEvent) TIDValue() uint32 {
+ return e.TID
+}
+
+func (e StreamEvent) FDValue() int32 {
+ return e.FD
+}
+
+func (e StreamEvent) LatencyValue() uint64 {
+ return e.DurationNs
+}
+
+func (e StreamEvent) GapValue() uint64 {
+ return e.GapNs
+}
+
+func (e StreamEvent) BytesValue() uint64 {
+ return e.Bytes
+}
+
+func (e StreamEvent) ReturnValue() int64 {
+ return e.RetVal
+}
+
+func (e StreamEvent) ErrorValue() bool {
+ return e.IsError
+}
+
// UnknownFD marks events that are not associated with a file descriptor.
const UnknownFD int32 = -1