somewhat working ebpf bpftrace

This commit is contained in:
Harshavardhan Musanalli
2025-11-08 20:42:07 +01:00
parent 190e54dd38
commit 794111cb44
16 changed files with 2834 additions and 216 deletions

343
ebpf_event_parser.go Normal file
View 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
}