summaryrefslogtreecommitdiff
path: root/internal/statsengine/process.go
blob: 296312bc06e883e4dcadaa13040e606af786468a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package statsengine

import (
	"ior/internal/event"
	"sort"
	"time"
)

type processAccumulator struct {
	byPID map[uint32]*processStats
}

type processStats struct {
	pid          uint32
	comm         string
	count        uint64
	totalBytes   uint64
	totalLatency uint64
}

func newProcessAccumulator() *processAccumulator {
	return &processAccumulator{byPID: make(map[uint32]*processStats)}
}

func (a *processAccumulator) Add(pair *event.Pair) {
	if a == nil || pair == nil || pair.EnterEv == nil {
		return
	}

	pid := pair.EnterEv.GetPid()
	stats := a.byPID[pid]
	if stats == nil {
		stats = &processStats{pid: pid}
		a.byPID[pid] = stats
	}
	if pair.Comm != "" && stats.comm != "" && stats.comm != pair.Comm {
		// Best-effort PID reuse handling: when command name changes for an
		// existing PID, treat it as a new process lifetime and reset counters.
		stats = &processStats{pid: pid}
		a.byPID[pid] = stats
	}

	stats.count++
	stats.totalBytes += pair.Bytes
	stats.totalLatency += pair.Duration
	if pair.Comm != "" {
		stats.comm = pair.Comm
	}
}

func (a *processAccumulator) Snapshot(elapsed time.Duration) []ProcessSnapshot {
	if a == nil {
		return nil
	}

	rateDiv := elapsed.Seconds()
	result := make([]ProcessSnapshot, 0, len(a.byPID))
	for _, stats := range a.byPID {
		result = append(result, stats.toSnapshot(rateDiv))
	}

	sort.Slice(result, func(i, j int) bool {
		if result[i].Syscalls != result[j].Syscalls {
			return result[i].Syscalls > result[j].Syscalls
		}
		if result[i].Bytes != result[j].Bytes {
			return result[i].Bytes > result[j].Bytes
		}
		return result[i].PID < result[j].PID
	})

	return result
}

func (s *processStats) toSnapshot(rateDiv float64) ProcessSnapshot {
	avg := 0.0
	if s.count > 0 {
		avg = float64(s.totalLatency) / float64(s.count)
	}

	return ProcessSnapshot{
		PID:          s.pid,
		Comm:         s.comm,
		Syscalls:     s.count,
		RatePerSec:   safeRate(s.count, rateDiv),
		Bytes:        s.totalBytes,
		AvgLatencyNs: avg,
	}
}