diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-08 20:10:20 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-08 20:10:20 +0200 |
| commit | 21aa0cd0f96087fa040750643109c496e7a1b3ee (patch) | |
| tree | edb29ed949cf638d5c2a759dd3bf8840fed45922 /internal/tui/eventstream | |
| parent | 7ad3bb96f4d07bdd8b20b561257a84c7f18c3829 (diff) | |
task 366: extract shared global filter types
Diffstat (limited to 'internal/tui/eventstream')
| -rw-r--r-- | internal/tui/eventstream/filter.go | 230 | ||||
| -rw-r--r-- | internal/tui/eventstream/model.go | 37 | ||||
| -rw-r--r-- | internal/tui/eventstream/streamevent.go | 44 |
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 |
