The Pedigree Project 0.1
ActivityDiagnostics.cc
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 */
8
9#include "pedigree/kernel/ActivityDiagnostics.h"
10
11#if PEDIGREE_ACTIVITY_DIAGNOSTICS
12
13#include "pedigree/kernel/time/Time.h"
14
15namespace ActivityDiagnostics {
16namespace {
17
18uint64_t g_InterruptCount = 0;
19uint64_t g_ExceptionCount = 0;
20uint64_t g_HardwareInterruptCount = 0;
21uint64_t g_OtherInterruptCount = 0;
22uint64_t g_InterruptVectorCounts[InterruptVectorCount] = {};
23uint64_t g_InterruptDurationBuckets[DurationBucketCount] = {};
24uint64_t g_PageFaultDurationBuckets[DurationBucketCount] = {};
25uint64_t g_SchedulerTimerDurationBuckets[DurationBucketCount] = {};
26uint64_t g_HardDispatchCount = 0;
27uint64_t g_HardDurationBuckets[DurationBucketCount] = {};
28uint64_t g_ThreadedDispatchCount = 0;
29uint64_t g_ThreadedDurationBuckets[DurationBucketCount] = {};
30uint64_t g_SchedulerTimerTicks = 0;
31uint64_t g_ScheduleCalls = 0;
32uint64_t g_SameThreadSelections = 0;
33uint64_t g_ContextSwitches = 0;
34uint64_t g_IdleSelections = 0;
35uint64_t g_SchedulerIdleFallbacks = 0;
36uint64_t g_SchedulerIdleFallbackCurrentReady = 0;
37uint64_t g_SchedulerIdleFallbackCurrentPending = 0;
38uint64_t g_SchedulerNoEligibleSelections = 0;
39uint64_t g_ReadyQueueScanEntries = 0;
40uint64_t g_ReadyQueueCandidateVisits = 0;
41uint64_t g_ReadyQueuePredicateRejects = 0;
42uint64_t g_ReadyQueueSelectionSampleCounter = 0;
43uint64_t g_ReadyQueueSelectionSamples = 0;
44uint64_t g_ReadyQueueSelectionDurationBuckets[DurationBucketCount] = {};
45uint64_t g_TimeAccountingSampleCounter = 0;
46uint64_t g_TimeAccountingSamples = 0;
47uint64_t g_TimeAccountingDurationBuckets[DurationBucketCount] = {};
48uint64_t g_IdleHaltEntries = 0;
49uint64_t g_FramebufferFlips = 0;
50uint64_t g_FramebufferCells = 0;
51uint64_t g_FramebufferDurationBuckets[DurationBucketCount] = {};
52uint64_t g_UserReturnStageSamples[UserReturnStageCount] = {};
53uint64_t g_UserReturnStageTotalNanoseconds[UserReturnStageCount] = {};
54uint64_t g_UserReturnStageDurationBuckets[UserReturnStageCount][DurationBucketCount] = {};
55uint64_t g_UserReturnFaultHandledSamples = 0;
56uint64_t g_UserReturnFaultFallbackSamples = 0;
57uint64_t g_UserReturnInterruptAffinityWaitedSamples = 0;
58uint64_t g_UserReturnSyscallAffinityWaitedSamples = 0;
59uint64_t g_UserEntryCaptureSamples = 0;
60uint64_t g_UserEntryRestoreSamples = 0;
61uint64_t g_UserEntryCaptureTscTotal = 0;
62uint64_t g_UserEntryRestoreTscTotal = 0;
63uint64_t g_UserEntryEmptyTscSamples = 0;
64uint64_t g_UserEntryEmptyTscTotal = 0;
65uint64_t g_UserEntryCaptureTscBuckets[DurationBucketCount] = {};
66uint64_t g_UserEntryRestoreTscBuckets[DurationBucketCount] = {};
67uint64_t g_UserEntryEmptyTscBuckets[DurationBucketCount] = {};
68
69size_t durationBucket(uint64_t duration) {
70 size_t bucket = 0;
71 uint64_t limit = 1000;
72 while (bucket + 1 < DurationBucketCount && duration >= limit) {
73 ++bucket;
74 limit <<= 1;
75 }
76 return bucket;
77}
78
79void recordDuration(uint64_t* buckets, uint64_t duration) {
80 __atomic_fetch_add(&buckets[durationBucket(duration)], static_cast<uint64_t>(1),
81 __ATOMIC_RELAXED);
82}
83
84size_t tscDurationBucket(uint64_t duration) {
85 size_t bucket = 0;
86 uint64_t limit = 64;
87 while (bucket + 1 < DurationBucketCount && duration >= limit) {
88 ++bucket;
89 limit <<= 1;
90 }
91 return bucket;
92}
93
94void recordTscDuration(uint64_t* buckets, uint64_t duration) {
95 __atomic_fetch_add(&buckets[tscDurationBucket(duration)], static_cast<uint64_t>(1),
96 __ATOMIC_RELAXED);
97}
98
99uint64_t load(const uint64_t& value) {
100 return __atomic_load_n(&value, __ATOMIC_ACQUIRE);
101}
102
103} // namespace
104
105#if PEDIGREE_X64_USER_ENTRY_DIAGNOSTICS
106extern "C" uint64_t pedigree_user_entry_capture_calls;
107extern "C" uint64_t pedigree_user_entry_restore_calls;
108#endif
109
110uint64_t timestamp() {
111 return Time::getTicks();
112}
113
114void snapshot(Snapshot& result) {
115 result.interruptCount = load(g_InterruptCount);
116 result.exceptionCount = load(g_ExceptionCount);
117 result.hardwareInterruptCount = load(g_HardwareInterruptCount);
118 result.otherInterruptCount = load(g_OtherInterruptCount);
119 for (size_t i = 0; i < InterruptVectorCount; ++i)
120 result.interruptVectorCounts[i] = load(g_InterruptVectorCounts[i]);
121 for (size_t i = 0; i < DurationBucketCount; ++i)
122 result.interruptDurationBuckets[i] = load(g_InterruptDurationBuckets[i]);
123 for (size_t i = 0; i < DurationBucketCount; ++i)
124 result.pageFaultDurationBuckets[i] = load(g_PageFaultDurationBuckets[i]);
125 for (size_t i = 0; i < DurationBucketCount; ++i)
126 result.schedulerTimerDurationBuckets[i] = load(g_SchedulerTimerDurationBuckets[i]);
127 result.hardDispatchCount = load(g_HardDispatchCount);
128 for (size_t i = 0; i < DurationBucketCount; ++i)
129 result.hardDurationBuckets[i] = load(g_HardDurationBuckets[i]);
130 result.threadedDispatchCount = load(g_ThreadedDispatchCount);
131 for (size_t i = 0; i < DurationBucketCount; ++i)
132 result.threadedDurationBuckets[i] = load(g_ThreadedDurationBuckets[i]);
133 result.schedulerTimerTicks = load(g_SchedulerTimerTicks);
134 result.scheduleCalls = load(g_ScheduleCalls);
135 result.sameThreadSelections = load(g_SameThreadSelections);
136 result.contextSwitches = load(g_ContextSwitches);
137 result.idleSelections = load(g_IdleSelections);
138 result.schedulerIdleFallbacks = load(g_SchedulerIdleFallbacks);
139 result.schedulerIdleFallbackCurrentReady = load(g_SchedulerIdleFallbackCurrentReady);
140 result.schedulerIdleFallbackCurrentPending = load(g_SchedulerIdleFallbackCurrentPending);
141 result.schedulerNoEligibleSelections = load(g_SchedulerNoEligibleSelections);
142 result.readyQueueScanEntries = load(g_ReadyQueueScanEntries);
143 result.readyQueueCandidateVisits = load(g_ReadyQueueCandidateVisits);
144 result.readyQueuePredicateRejects = load(g_ReadyQueuePredicateRejects);
145 result.readyQueueSelectionSamples = load(g_ReadyQueueSelectionSamples);
146 for (size_t i = 0; i < DurationBucketCount; ++i)
147 result.readyQueueSelectionDurationBuckets[i] = load(g_ReadyQueueSelectionDurationBuckets[i]);
148 result.timeAccountingSamples = load(g_TimeAccountingSamples);
149 for (size_t i = 0; i < DurationBucketCount; ++i)
150 result.timeAccountingDurationBuckets[i] = load(g_TimeAccountingDurationBuckets[i]);
151 result.idleHaltEntries = load(g_IdleHaltEntries);
152 result.framebufferFlips = load(g_FramebufferFlips);
153 result.framebufferCells = load(g_FramebufferCells);
154 for (size_t i = 0; i < DurationBucketCount; ++i)
155 result.framebufferDurationBuckets[i] = load(g_FramebufferDurationBuckets[i]);
156 for (size_t stage = 0; stage < UserReturnStageCount; ++stage) {
157 result.userReturnStageSamples[stage] = load(g_UserReturnStageSamples[stage]);
158 result.userReturnStageTotalNanoseconds[stage] = load(g_UserReturnStageTotalNanoseconds[stage]);
159 for (size_t i = 0; i < DurationBucketCount; ++i) {
160 result.userReturnStageDurationBuckets[stage][i] =
161 load(g_UserReturnStageDurationBuckets[stage][i]);
162 }
163 }
164 result.userReturnFaultHandledSamples = load(g_UserReturnFaultHandledSamples);
165 result.userReturnFaultFallbackSamples = load(g_UserReturnFaultFallbackSamples);
166 result.userReturnInterruptAffinityWaitedSamples =
167 load(g_UserReturnInterruptAffinityWaitedSamples);
168 result.userReturnSyscallAffinityWaitedSamples = load(g_UserReturnSyscallAffinityWaitedSamples);
169#if PEDIGREE_X64_USER_ENTRY_DIAGNOSTICS
170 result.userEntryCaptureCalls = pedigree_user_entry_capture_calls;
171 result.userEntryRestoreCalls = pedigree_user_entry_restore_calls;
172#else
173 result.userEntryCaptureCalls = 0;
174 result.userEntryRestoreCalls = 0;
175#endif
176 result.userEntryCaptureSamples = load(g_UserEntryCaptureSamples);
177 result.userEntryRestoreSamples = load(g_UserEntryRestoreSamples);
178 result.userEntryCaptureTscTotal = load(g_UserEntryCaptureTscTotal);
179 result.userEntryRestoreTscTotal = load(g_UserEntryRestoreTscTotal);
180 result.userEntryEmptyTscSamples = load(g_UserEntryEmptyTscSamples);
181 result.userEntryEmptyTscTotal = load(g_UserEntryEmptyTscTotal);
182 for (size_t i = 0; i < DurationBucketCount; ++i) {
183 result.userEntryCaptureTscBuckets[i] = load(g_UserEntryCaptureTscBuckets[i]);
184 result.userEntryRestoreTscBuckets[i] = load(g_UserEntryRestoreTscBuckets[i]);
185 result.userEntryEmptyTscBuckets[i] = load(g_UserEntryEmptyTscBuckets[i]);
186 }
187}
188
189void recordInterruptEntry(size_t vector) {
190 if (vector >= InterruptVectorCount)
191 return;
192 __atomic_fetch_add(&g_InterruptCount, static_cast<uint64_t>(1), __ATOMIC_RELAXED);
193 __atomic_fetch_add(&g_InterruptVectorCounts[vector], static_cast<uint64_t>(1), __ATOMIC_RELAXED);
194 if (vector < 32) {
195 __atomic_fetch_add(&g_ExceptionCount, static_cast<uint64_t>(1), __ATOMIC_RELAXED);
196 } else if (vector < 48) {
197 __atomic_fetch_add(&g_HardwareInterruptCount, static_cast<uint64_t>(1), __ATOMIC_RELAXED);
198 } else {
199 __atomic_fetch_add(&g_OtherInterruptCount, static_cast<uint64_t>(1), __ATOMIC_RELAXED);
200 }
201}
202
203void recordInterruptDuration(size_t vector, uint64_t duration) {
204 if (vector >= InterruptVectorCount)
205 return;
206 recordDuration(g_InterruptDurationBuckets, duration);
207 if (vector == 14)
208 recordDuration(g_PageFaultDurationBuckets, duration);
209 else if (vector == 0xfe)
210 recordDuration(g_SchedulerTimerDurationBuckets, duration);
211}
212
213void recordHardDispatch(uint8_t irq, uint64_t duration) {
214 (void)irq;
215 __atomic_fetch_add(&g_HardDispatchCount, static_cast<uint64_t>(1), __ATOMIC_RELAXED);
216 recordDuration(g_HardDurationBuckets, duration);
217}
218
219void recordThreadedDispatch(uint8_t irq, uint64_t duration) {
220 (void)irq;
221 __atomic_fetch_add(&g_ThreadedDispatchCount, static_cast<uint64_t>(1), __ATOMIC_RELAXED);
222 recordDuration(g_ThreadedDurationBuckets, duration);
223}
224
225void recordSchedulerTimer() {
226 __atomic_fetch_add(&g_SchedulerTimerTicks, static_cast<uint64_t>(1), __ATOMIC_RELAXED);
227}
228
229void recordScheduleCall() {
230 __atomic_fetch_add(&g_ScheduleCalls, static_cast<uint64_t>(1), __ATOMIC_RELAXED);
231}
232
233void recordSameThreadSelection() {
234 __atomic_fetch_add(&g_SameThreadSelections, static_cast<uint64_t>(1), __ATOMIC_RELAXED);
235}
236
237void recordContextSwitch() {
238 __atomic_fetch_add(&g_ContextSwitches, static_cast<uint64_t>(1), __ATOMIC_RELAXED);
239}
240
241void recordIdleSelection() {
242 __atomic_fetch_add(&g_IdleSelections, static_cast<uint64_t>(1), __ATOMIC_RELAXED);
243}
244
245void recordSchedulerIdleFallback(bool currentReady, bool currentPending) {
246 __atomic_fetch_add(&g_SchedulerIdleFallbacks, static_cast<uint64_t>(1), __ATOMIC_RELAXED);
247 if (currentReady)
248 __atomic_fetch_add(&g_SchedulerIdleFallbackCurrentReady, static_cast<uint64_t>(1),
249 __ATOMIC_RELAXED);
250 if (currentPending)
251 __atomic_fetch_add(&g_SchedulerIdleFallbackCurrentPending, static_cast<uint64_t>(1),
252 __ATOMIC_RELAXED);
253}
254
255void recordSchedulerNoEligibleSelection() {
256 __atomic_fetch_add(&g_SchedulerNoEligibleSelections, static_cast<uint64_t>(1), __ATOMIC_RELAXED);
257}
258
259void recordReadyQueueScanEntry() {
260 __atomic_fetch_add(&g_ReadyQueueScanEntries, static_cast<uint64_t>(1), __ATOMIC_RELAXED);
261}
262
263void recordReadyQueueCandidateVisit() {
264 __atomic_fetch_add(&g_ReadyQueueCandidateVisits, static_cast<uint64_t>(1), __ATOMIC_RELAXED);
265}
266
267void recordReadyQueuePredicateReject() {
268 __atomic_fetch_add(&g_ReadyQueuePredicateRejects, static_cast<uint64_t>(1), __ATOMIC_RELAXED);
269}
270
271bool shouldSampleReadyQueueSelection() {
272 return (__atomic_fetch_add(&g_ReadyQueueSelectionSampleCounter, static_cast<uint64_t>(1),
273 __ATOMIC_RELAXED) &
274 63) == 0;
275}
276
277void recordReadyQueueSelection(uint64_t duration) {
278 __atomic_fetch_add(&g_ReadyQueueSelectionSamples, static_cast<uint64_t>(1), __ATOMIC_RELAXED);
279 recordDuration(g_ReadyQueueSelectionDurationBuckets, duration);
280}
281
282bool shouldSampleTimeAccounting() {
283 return (__atomic_fetch_add(&g_TimeAccountingSampleCounter, static_cast<uint64_t>(1),
284 __ATOMIC_RELAXED) &
285 63) == 0;
286}
287
288void recordTimeAccounting(uint64_t duration) {
289 __atomic_fetch_add(&g_TimeAccountingSamples, static_cast<uint64_t>(1), __ATOMIC_RELAXED);
290 recordDuration(g_TimeAccountingDurationBuckets, duration);
291}
292
293void recordIdleHalt() {
294 __atomic_fetch_add(&g_IdleHaltEntries, static_cast<uint64_t>(1), __ATOMIC_RELAXED);
295}
296
297void recordFramebufferFlip(size_t cells, uint64_t duration) {
298 __atomic_fetch_add(&g_FramebufferFlips, static_cast<uint64_t>(1), __ATOMIC_RELAXED);
299 __atomic_fetch_add(&g_FramebufferCells, static_cast<uint64_t>(cells), __ATOMIC_RELAXED);
300 recordDuration(g_FramebufferDurationBuckets, duration);
301}
302
303void recordUserReturnStage(UserReturnStage stage, uint64_t duration) {
304 const size_t index = static_cast<size_t>(stage);
305 if (index >= UserReturnStageCount)
306 return;
307 __atomic_fetch_add(&g_UserReturnStageSamples[index], static_cast<uint64_t>(1), __ATOMIC_RELAXED);
308 __atomic_fetch_add(&g_UserReturnStageTotalNanoseconds[index], duration, __ATOMIC_RELAXED);
309 recordDuration(g_UserReturnStageDurationBuckets[index], duration);
310}
311
312void recordUserReturnFaultOutcome(bool handled) {
313 uint64_t* counter =
314 handled ? &g_UserReturnFaultHandledSamples : &g_UserReturnFaultFallbackSamples;
315 __atomic_fetch_add(counter, static_cast<uint64_t>(1), __ATOMIC_RELAXED);
316}
317
318void recordUserReturnAffinityWait(bool syscall) {
319 uint64_t* counter = syscall ? &g_UserReturnSyscallAffinityWaitedSamples
320 : &g_UserReturnInterruptAffinityWaitedSamples;
321 __atomic_fetch_add(counter, static_cast<uint64_t>(1), __ATOMIC_RELAXED);
322}
323
324#if PEDIGREE_X64_USER_ENTRY_DIAGNOSTICS
325extern "C" void pedigree_record_user_entry_capture(uint64_t duration, uint64_t emptyDuration) {
326 __atomic_fetch_add(&g_UserEntryCaptureSamples, static_cast<uint64_t>(1), __ATOMIC_RELAXED);
327 __atomic_fetch_add(&g_UserEntryCaptureTscTotal, duration, __ATOMIC_RELAXED);
328 recordTscDuration(g_UserEntryCaptureTscBuckets, duration);
329 __atomic_fetch_add(&g_UserEntryEmptyTscSamples, static_cast<uint64_t>(1), __ATOMIC_RELAXED);
330 __atomic_fetch_add(&g_UserEntryEmptyTscTotal, emptyDuration, __ATOMIC_RELAXED);
331 recordTscDuration(g_UserEntryEmptyTscBuckets, emptyDuration);
332}
333
334extern "C" void pedigree_record_user_entry_restore(uint64_t duration, uint64_t emptyDuration) {
335 __atomic_fetch_add(&g_UserEntryRestoreSamples, static_cast<uint64_t>(1), __ATOMIC_RELAXED);
336 __atomic_fetch_add(&g_UserEntryRestoreTscTotal, duration, __ATOMIC_RELAXED);
337 recordTscDuration(g_UserEntryRestoreTscBuckets, duration);
338 __atomic_fetch_add(&g_UserEntryEmptyTscSamples, static_cast<uint64_t>(1), __ATOMIC_RELAXED);
339 __atomic_fetch_add(&g_UserEntryEmptyTscTotal, emptyDuration, __ATOMIC_RELAXED);
340 recordTscDuration(g_UserEntryEmptyTscBuckets, emptyDuration);
341}
342#endif
343
344} // namespace ActivityDiagnostics
345
346#endif