The Pedigree Project 0.1
accounting-regressions.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/Atomic.h"
3#include "pedigree/kernel/Log.h"
4#include "pedigree/kernel/process/PerProcessorScheduler.h"
5#include "pedigree/kernel/process/Process.h"
6#include "pedigree/kernel/process/Scheduler.h"
7#include "pedigree/kernel/process/Thread.h"
8#include "pedigree/kernel/processor/Processor.h"
9#include "pedigree/kernel/processor/ProcessorInformation.h"
10#include "pedigree/kernel/time/Time.h"
11
12namespace {
13class HostedAccountingProcess : public Process {
14 public:
15 explicit HostedAccountingProcess(Thread* driver, size_t interest = ~size_t(0))
16 : Process(DeferredPublication(), driver->getParent()),
17 m_Driver(driver),
18 calls(0),
19 user(0),
20 profile(0),
21 failures(0) {
23 description() += "hosted deferred accounting probe";
24 publish();
25 }
26
27 ~HostedAccountingProcess() override {
29 }
30
31 void setReportInterest(size_t interest, bool enabled) {
32 setTimeAccountingReportInterest(interest, enabled);
33 }
34
35 Thread* m_Driver;
36 Atomic<size_t> calls;
37 Atomic<size_t> user;
38 Atomic<size_t> profile;
39 Atomic<size_t> failures;
40
41 private:
42 void reportTimesUpdated(Time::Timestamp userTotal, Time::Timestamp profileTotal) override {
43 Thread* current = Processor::information().getCurrentThread();
44 const size_t processCount = Scheduler::instance().getNumProcesses();
45 const size_t threadCount = getNumThreads();
46 (void)threadCount;
48 current == m_Driver || !processCount || userTotal < user || profileTotal < profile) {
49 failures += 1;
50 }
51 user = static_cast<size_t>(userTotal);
52 profile = static_cast<size_t>(profileTotal);
53 calls += 1;
54 }
55};
56
57struct AccountingThreadContext {
58 AccountingThreadContext(Process* process, Time::Timestamp kernelBefore)
59 : process(process),
60 kernelBefore(kernelBefore),
61 user(0),
62 kernel(0),
63 ran(0),
64 firstSliceAccounted(0) {}
65
66 Process* process;
67 Time::Timestamp kernelBefore;
68 Time::Timestamp user;
69 Time::Timestamp kernel;
71 Atomic<size_t> firstSliceAccounted;
72};
73
74int accountedKernelThread(void* parameter) {
75 AccountingThreadContext* context = reinterpret_cast<AccountingThreadContext*>(parameter);
76#if PEDIGREE_TIME_ACCOUNTING && PEDIGREE_SAMPLED_TIME_ACCOUNTING
77 const Time::Timestamp deadline = Time::getTicks() + 2 * Time::Multiplier::Second;
78 while (context->process->getKernelTime() == context->kernelBefore &&
79 Time::getTicks() < deadline) {
81 }
82#else
84#endif
85 context->firstSliceAccounted = context->process->getKernelTime() > context->kernelBefore;
86 Thread* current = Processor::information().getCurrentThread();
87 current->publishTimeAccountingForHostedTest(29, 17);
88 context->user = current->getUserTime();
89 context->kernel = current->getKernelTime();
90 context->ran = 1;
91 return 0;
92}
93
94} // namespace
95
96bool runHostedAccountingRegressions() {
97 Thread* driver = Processor::information().getCurrentThread();
98 const bool interruptsWereEnabled = Processor::getInterrupts();
100 const Time::Timestamp threadUser = driver->getUserTime();
101 const Time::Timestamp threadKernel = driver->getKernelTime();
102 const Time::Timestamp processUser = driver->getParent()->getUserTime();
103 const Time::Timestamp processKernel = driver->getParent()->getKernelTime();
104 driver->publishTimeAccountingForHostedTest(13, 7);
105 driver->publishTimeAccountingForHostedTest(0, 0);
106 const bool exactThreadPublication = !Processor::getInterrupts() &&
107 driver->getUserTime() == threadUser + 13 &&
108 driver->getKernelTime() == threadKernel + 7 &&
109 driver->getParent()->getUserTime() == processUser + 13 &&
110 driver->getParent()->getKernelTime() == processKernel + 7;
111 bool sampledTickAccounting = true;
112#if PEDIGREE_TIME_ACCOUNTING && PEDIGREE_SAMPLED_TIME_ACCOUNTING
113 const CpuTimeMode originalMode = driver->currentTimeAccountingMode();
114 driver->recordTime(CpuTimeMode::User);
115 driver->trackTime(CpuTimeMode::User);
116 driver->transitionTime(CpuTimeMode::User, CpuTimeMode::Kernel, true);
117 sampledTickAccounting &= driver->currentTimeAccountingMode() == CpuTimeMode::Kernel;
118 driver->trackTime(CpuTimeMode::Kernel);
119 driver->transitionTimeAtInterruptReturn(CpuTimeMode::Kernel, CpuTimeMode::User);
120 sampledTickAccounting &=
121 !Processor::getInterrupts() && driver->currentTimeAccountingMode() == CpuTimeMode::User &&
122 driver->getUserTime() == threadUser + 13 && driver->getKernelTime() == threadKernel + 7 &&
123 driver->getParent()->getUserTime() == processUser + 13 &&
124 driver->getParent()->getKernelTime() == processKernel + 7;
125
126 driver->recordTime(CpuTimeMode::Kernel);
127 driver->accountTimerTick(17, false);
128 sampledTickAccounting &= driver->currentTimeAccountingMode() == CpuTimeMode::Kernel &&
129 driver->getUserTime() == threadUser + 30 &&
130 driver->getKernelTime() == threadKernel + 7 &&
131 driver->getParent()->getUserTime() == processUser + 30 &&
132 driver->getParent()->getKernelTime() == processKernel + 7;
133 driver->accountTimerTick(0, false);
134 driver->accountTimerTick(0, true);
135 driver->recordTime(CpuTimeMode::User);
136 driver->accountTimerTick(23, true);
137 sampledTickAccounting &=
138 !Processor::getInterrupts() && driver->currentTimeAccountingMode() == CpuTimeMode::User &&
139 driver->getUserTime() == threadUser + 30 && driver->getKernelTime() == threadKernel + 30 &&
140 driver->getParent()->getUserTime() == processUser + 30 &&
141 driver->getParent()->getKernelTime() == processKernel + 30;
142 driver->recordTime(originalMode);
143#endif
144 Processor::setInterrupts(interruptsWereEnabled);
145
146 const bool loadRequestPassed = Scheduler::instance().runHostedLoadAverageRequestRegression();
147 HostedAccountingProcess* dormant = new HostedAccountingProcess(driver, 0);
148 dormant->publishTimeAccountingForHostedTest(100, 200);
149 bool interestPassed = !dormant->timeAccountingPendingForHostedTest() &&
150 !dormant->timeAccountingInterestForHostedTest() &&
151 dormant->getUserTime() == 100 && dormant->getKernelTime() == 200;
152 dormant->setReportInterest(1, true);
153 for (size_t attempt = 0; !dormant->calls && attempt < 10000; ++attempt) {
154 PerProcessorScheduler::serviceCurrentIrqWorkDoorbellForTest();
156 }
157 interestPassed &=
158 dormant->calls == 1 && dormant->user == 100 && dormant->profile == 300 && !dormant->failures;
159 dormant->setReportInterest(2, true);
160 dormant->setReportInterest(1, false);
161 dormant->publishTimeAccountingForHostedTest(17, 11);
162 interestPassed &= dormant->timeAccountingInterestForHostedTest() == 2;
163 for (size_t attempt = 0; dormant->calls < 2 && attempt < 10000; ++attempt) {
164 PerProcessorScheduler::serviceCurrentIrqWorkDoorbellForTest();
166 }
167 interestPassed &=
168 dormant->calls == 2 && dormant->user == 117 && dormant->profile == 328 && !dormant->failures;
169 dormant->setReportInterest(2, false);
170 dormant->closeTimeAccountingForHostedTest();
171 const size_t dormantCalls = dormant->calls;
172 dormant->publishTimeAccountingForHostedTest(3, 5);
173 interestPassed &= !dormant->timeAccountingInterestForHostedTest() &&
174 !dormant->timeAccountingPendingForHostedTest() &&
175 dormant->calls == dormantCalls && dormant->getUserTime() == 120 &&
176 dormant->getKernelTime() == 216;
177 delete dormant;
178
179 HostedAccountingProcess* process = new HostedAccountingProcess(driver);
180
182 process->publishTimeAccountingForHostedTest(13, 7);
183 Processor::setInterrupts(interruptsWereEnabled);
184
185 constexpr size_t Attempts = 10000;
186 for (size_t attempt = 0; !process->calls && attempt < Attempts; ++attempt) {
187 PerProcessorScheduler::serviceCurrentIrqWorkDoorbellForTest();
189 }
190
191 const bool exactWorkerBatch =
192 process->calls == 1 && process->user == 13 && process->profile == 20 && !process->failures;
193 process->publishTimeAccountingForHostedTest(0, 0);
194 const bool zeroBatchDiscarded = !process->timeAccountingPendingForHostedTest() &&
195 process->getUserTime() == 13 && process->getKernelTime() == 7;
196
197 AccountingThreadContext threadContext(process, process->getKernelTime());
198 Thread* accountedThread =
199 new Thread(process, accountedKernelThread, &threadContext, nullptr, false, true, true);
200 accountedThread->setName("hosted accounting first-slice probe");
201 const bool accountedThreadStarted = accountedThread->start();
202 const bool accountedThreadJoined = accountedThreadStarted && accountedThread->joinForCompletion();
203 if (!accountedThreadStarted) {
204 delete accountedThread;
205 }
206
207 for (size_t attempt = 0; (process->user != 42 || process->profile == 20) && attempt < Attempts;
208 ++attempt) {
209 PerProcessorScheduler::serviceCurrentIrqWorkDoorbellForTest();
211 }
212 const bool firstKernelSliceAccounted = accountedThreadStarted && accountedThreadJoined &&
213 threadContext.ran && threadContext.firstSliceAccounted &&
214 process->user == 42 && process->profile > 20 &&
215 !process->failures;
216 const bool retiredThreadAccounted = accountedThreadJoined && !process->getNumThreads() &&
217 process->getUserTime() == 13 + threadContext.user &&
218 process->getKernelTime() >= 7 + threadContext.kernel;
219 if (!firstKernelSliceAccounted) {
220 ERROR("HOSTED-ACCOUNTING-FIRST: started="
221 << accountedThreadStarted << " joined=" << accountedThreadJoined
222 << " ran=" << static_cast<size_t>(threadContext.ran)
223 << " slice=" << static_cast<size_t>(threadContext.firstSliceAccounted));
224 ERROR("HOSTED-ACCOUNTING-FIRST: user=" << static_cast<size_t>(process->user) << " profile="
225 << static_cast<size_t>(process->profile) << " failures="
226 << static_cast<size_t>(process->failures));
227 }
228
229 process->closeTimeAccountingForHostedTest();
230 const size_t callsBeforeLatePublication = process->calls;
231 const Time::Timestamp userBeforeLatePublication = process->getUserTime();
232 const Time::Timestamp kernelBeforeLatePublication = process->getKernelTime();
233 process->publishTimeAccountingForHostedTest(101, 211);
234 for (size_t attempt = 0; attempt < 32; ++attempt) {
236 }
237 const bool latePublicationDiscarded =
238 process->calls == callsBeforeLatePublication &&
239 !process->timeAccountingPendingForHostedTest() &&
240 process->timeAccountingInterestForHostedTest() &&
241 process->getUserTime() == userBeforeLatePublication + 101 &&
242 process->getKernelTime() == kernelBeforeLatePublication + 211;
243 delete process;
244
245 const bool passed = exactThreadPublication && sampledTickAccounting && loadRequestPassed &&
246 interestPassed && exactWorkerBatch && zeroBatchDiscarded &&
247 firstKernelSliceAccounted && retiredThreadAccounted &&
248 latePublicationDiscarded;
249 if (!passed) {
250 ERROR("HOSTED-WAIT-TEST: FAIL deferred-time-accounting-worker: exact="
251 << exactThreadPublication << " sampled=" << sampledTickAccounting << " load="
252 << loadRequestPassed << " interest=" << interestPassed << " batch=" << exactWorkerBatch
253 << " zero=" << zeroBatchDiscarded << " first=" << firstKernelSliceAccounted
254 << " retired=" << retiredThreadAccounted << " late=" << latePublicationDiscarded);
255 } else {
256 NOTICE("HOSTED-WAIT-TEST: PASS deferred-time-accounting-worker");
257 }
258 return passed;
259}
virtual void reportTimesUpdated(Time::Timestamp userTotal, Time::Timestamp total)
Definition Process.h:984
size_t getNumThreads()
Definition Process.cc:1380
void publish()
Definition Process.cc:944
LargeStaticString & description()
Definition Process.h:525
Time::Timestamp getUserTime() const
Definition Process.h:829
void prepareForDestruction()
Definition Process.cc:1029
void setTimeAccountingReportInterest(size_t interest, bool enabled)
Definition Process.cc:844
void enableTimeAccountingReports(size_t initialInterest=~size_t(0))
Definition Process.cc:839
static bool getInterrupts()
static ProcessorInformation & information()
static void pause()
static bool inDeviceHardIrq()
Definition Processor.h:581
static void setInterrupts(bool bEnable)
static Scheduler & instance()
Definition Scheduler.h:96
size_t getNumProcesses()
Definition Scheduler.cc:268
void yield()
Definition Scheduler.cc:236
void recordTime(CpuTimeMode mode)
Definition Thread.cc:384
void accountTimerTick(Time::Timestamp delta, bool kernelMode)
Definition Thread.cc:422
CpuTimeMode currentTimeAccountingMode() const
Definition Thread.cc:471
Time::Timestamp getUserTime() const
Definition Thread.h:412
bool joinForCompletion()
Definition Thread.cc:2750
void trackTime(CpuTimeMode mode)
Definition Thread.cc:392
ALWAYS_INLINE void transitionTimeAtInterruptReturn(CpuTimeMode from, CpuTimeMode to)
Definition Thread.h:373
Process * getParent() const
Definition Thread.h:340
bool start()
Definition Thread.cc:751
void transitionTime(CpuTimeMode from, CpuTimeMode to, bool interruptsAlreadyDisabled=false)
Definition Thread.cc:405