somewhat working ebpf bpftrace
This commit is contained in:
343
ebpf_event_parser.go
Normal file
343
ebpf_event_parser.go
Normal file
@@ -0,0 +1,343 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// EventScanner parses bpftrace output and converts it to TraceEvent structs
|
||||
type EventScanner struct {
|
||||
scanner *bufio.Scanner
|
||||
lastEvent *TraceEvent
|
||||
lineRegex *regexp.Regexp
|
||||
}
|
||||
|
||||
// NewEventScanner creates a new event scanner for parsing bpftrace output
|
||||
func NewEventScanner(reader io.Reader) *EventScanner {
|
||||
// Regex pattern to match our trace output format:
|
||||
// TRACE|timestamp|pid|tid|comm|function|message
|
||||
pattern := `^TRACE\|(\d+)\|(\d+)\|(\d+)\|([^|]+)\|([^|]+)\|(.*)$`
|
||||
regex, _ := regexp.Compile(pattern)
|
||||
|
||||
return &EventScanner{
|
||||
scanner: bufio.NewScanner(reader),
|
||||
lineRegex: regex,
|
||||
}
|
||||
}
|
||||
|
||||
// Scan advances the scanner to the next event
|
||||
func (es *EventScanner) Scan() bool {
|
||||
for es.scanner.Scan() {
|
||||
line := strings.TrimSpace(es.scanner.Text())
|
||||
|
||||
// Skip empty lines and non-trace lines
|
||||
if line == "" || !strings.HasPrefix(line, "TRACE|") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse the trace line
|
||||
if event := es.parseLine(line); event != nil {
|
||||
es.lastEvent = event
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Event returns the most recently parsed event
|
||||
func (es *EventScanner) Event() *TraceEvent {
|
||||
return es.lastEvent
|
||||
}
|
||||
|
||||
// Error returns any scanning error
|
||||
func (es *EventScanner) Error() error {
|
||||
return es.scanner.Err()
|
||||
}
|
||||
|
||||
// parseLine parses a single trace line into a TraceEvent
|
||||
func (es *EventScanner) parseLine(line string) *TraceEvent {
|
||||
matches := es.lineRegex.FindStringSubmatch(line)
|
||||
if len(matches) != 7 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse timestamp (nanoseconds)
|
||||
timestamp, err := strconv.ParseInt(matches[1], 10, 64)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse PID
|
||||
pid, err := strconv.Atoi(matches[2])
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse TID
|
||||
tid, err := strconv.Atoi(matches[3])
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Extract process name, function, and message
|
||||
processName := strings.TrimSpace(matches[4])
|
||||
function := strings.TrimSpace(matches[5])
|
||||
message := strings.TrimSpace(matches[6])
|
||||
|
||||
event := &TraceEvent{
|
||||
Timestamp: timestamp,
|
||||
PID: pid,
|
||||
TID: tid,
|
||||
ProcessName: processName,
|
||||
Function: function,
|
||||
Message: message,
|
||||
RawArgs: make(map[string]string),
|
||||
}
|
||||
|
||||
// Try to extract additional information from the message
|
||||
es.enrichEvent(event, message)
|
||||
|
||||
return event
|
||||
}
|
||||
|
||||
// enrichEvent extracts additional information from the message
|
||||
func (es *EventScanner) enrichEvent(event *TraceEvent, message string) {
|
||||
// Parse common patterns in messages to extract arguments
|
||||
// This is a simplified version - in a real implementation you'd want more sophisticated parsing
|
||||
|
||||
// Look for patterns like "arg1=value, arg2=value"
|
||||
argPattern := regexp.MustCompile(`(\w+)=([^,\s]+)`)
|
||||
matches := argPattern.FindAllStringSubmatch(message, -1)
|
||||
|
||||
for _, match := range matches {
|
||||
if len(match) == 3 {
|
||||
event.RawArgs[match[1]] = match[2]
|
||||
}
|
||||
}
|
||||
|
||||
// Look for numeric patterns that might be syscall arguments
|
||||
numberPattern := regexp.MustCompile(`\b(\d+)\b`)
|
||||
numbers := numberPattern.FindAllString(message, -1)
|
||||
|
||||
for i, num := range numbers {
|
||||
argName := "arg" + strconv.Itoa(i+1)
|
||||
event.RawArgs[argName] = num
|
||||
}
|
||||
}
|
||||
|
||||
// TraceEventFilter provides filtering capabilities for trace events
|
||||
type TraceEventFilter struct {
|
||||
MinTimestamp int64
|
||||
MaxTimestamp int64
|
||||
ProcessNames []string
|
||||
PIDs []int
|
||||
UIDs []int
|
||||
Functions []string
|
||||
MessageFilter string
|
||||
}
|
||||
|
||||
// ApplyFilter applies filters to a slice of events
|
||||
func (filter *TraceEventFilter) ApplyFilter(events []TraceEvent) []TraceEvent {
|
||||
if filter == nil {
|
||||
return events
|
||||
}
|
||||
|
||||
var filtered []TraceEvent
|
||||
|
||||
for _, event := range events {
|
||||
if filter.matchesEvent(&event) {
|
||||
filtered = append(filtered, event)
|
||||
}
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
|
||||
// matchesEvent checks if an event matches the filter criteria
|
||||
func (filter *TraceEventFilter) matchesEvent(event *TraceEvent) bool {
|
||||
// Check timestamp range
|
||||
if filter.MinTimestamp > 0 && event.Timestamp < filter.MinTimestamp {
|
||||
return false
|
||||
}
|
||||
if filter.MaxTimestamp > 0 && event.Timestamp > filter.MaxTimestamp {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check process names
|
||||
if len(filter.ProcessNames) > 0 {
|
||||
found := false
|
||||
for _, name := range filter.ProcessNames {
|
||||
if strings.Contains(event.ProcessName, name) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check PIDs
|
||||
if len(filter.PIDs) > 0 {
|
||||
found := false
|
||||
for _, pid := range filter.PIDs {
|
||||
if event.PID == pid {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check UIDs
|
||||
if len(filter.UIDs) > 0 {
|
||||
found := false
|
||||
for _, uid := range filter.UIDs {
|
||||
if event.UID == uid {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check functions
|
||||
if len(filter.Functions) > 0 {
|
||||
found := false
|
||||
for _, function := range filter.Functions {
|
||||
if strings.Contains(event.Function, function) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check message filter
|
||||
if filter.MessageFilter != "" {
|
||||
if !strings.Contains(event.Message, filter.MessageFilter) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// TraceEventAggregator provides aggregation capabilities for trace events
|
||||
type TraceEventAggregator struct {
|
||||
events []TraceEvent
|
||||
}
|
||||
|
||||
// NewTraceEventAggregator creates a new event aggregator
|
||||
func NewTraceEventAggregator(events []TraceEvent) *TraceEventAggregator {
|
||||
return &TraceEventAggregator{
|
||||
events: events,
|
||||
}
|
||||
}
|
||||
|
||||
// CountByProcess returns event counts grouped by process
|
||||
func (agg *TraceEventAggregator) CountByProcess() map[string]int {
|
||||
counts := make(map[string]int)
|
||||
for _, event := range agg.events {
|
||||
counts[event.ProcessName]++
|
||||
}
|
||||
return counts
|
||||
}
|
||||
|
||||
// CountByFunction returns event counts grouped by function
|
||||
func (agg *TraceEventAggregator) CountByFunction() map[string]int {
|
||||
counts := make(map[string]int)
|
||||
for _, event := range agg.events {
|
||||
counts[event.Function]++
|
||||
}
|
||||
return counts
|
||||
}
|
||||
|
||||
// CountByPID returns event counts grouped by PID
|
||||
func (agg *TraceEventAggregator) CountByPID() map[int]int {
|
||||
counts := make(map[int]int)
|
||||
for _, event := range agg.events {
|
||||
counts[event.PID]++
|
||||
}
|
||||
return counts
|
||||
}
|
||||
|
||||
// GetTimeRange returns the time range of events
|
||||
func (agg *TraceEventAggregator) GetTimeRange() (int64, int64) {
|
||||
if len(agg.events) == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
minTime := agg.events[0].Timestamp
|
||||
maxTime := agg.events[0].Timestamp
|
||||
|
||||
for _, event := range agg.events {
|
||||
if event.Timestamp < minTime {
|
||||
minTime = event.Timestamp
|
||||
}
|
||||
if event.Timestamp > maxTime {
|
||||
maxTime = event.Timestamp
|
||||
}
|
||||
}
|
||||
|
||||
return minTime, maxTime
|
||||
}
|
||||
|
||||
// GetEventRate calculates events per second
|
||||
func (agg *TraceEventAggregator) GetEventRate() float64 {
|
||||
if len(agg.events) < 2 {
|
||||
return 0
|
||||
}
|
||||
|
||||
minTime, maxTime := agg.GetTimeRange()
|
||||
durationNs := maxTime - minTime
|
||||
durationSeconds := float64(durationNs) / float64(time.Second)
|
||||
|
||||
if durationSeconds == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
return float64(len(agg.events)) / durationSeconds
|
||||
}
|
||||
|
||||
// GetTopProcesses returns the most active processes
|
||||
func (agg *TraceEventAggregator) GetTopProcesses(limit int) []ProcessStat {
|
||||
counts := agg.CountByProcess()
|
||||
total := len(agg.events)
|
||||
|
||||
var stats []ProcessStat
|
||||
for processName, count := range counts {
|
||||
percentage := float64(count) / float64(total) * 100
|
||||
stats = append(stats, ProcessStat{
|
||||
ProcessName: processName,
|
||||
EventCount: count,
|
||||
Percentage: percentage,
|
||||
})
|
||||
}
|
||||
|
||||
// Simple sorting by event count (bubble sort for simplicity)
|
||||
for i := 0; i < len(stats); i++ {
|
||||
for j := i + 1; j < len(stats); j++ {
|
||||
if stats[j].EventCount > stats[i].EventCount {
|
||||
stats[i], stats[j] = stats[j], stats[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if limit > 0 && limit < len(stats) {
|
||||
stats = stats[:limit]
|
||||
}
|
||||
|
||||
return stats
|
||||
}
|
||||
Reference in New Issue
Block a user