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), kernelBefore(kernelBefore), ran(0), firstSliceAccounted(0) {}
60
61 Process* process;
62 Time::Timestamp kernelBefore;
64 Atomic<size_t> firstSliceAccounted;
65};
66
67int accountedKernelThread(void* parameter) {
68 AccountingThreadContext* context = reinterpret_cast<AccountingThreadContext*>(parameter);
69#if PEDIGREE_TIME_ACCOUNTING && PEDIGREE_SAMPLED_TIME_ACCOUNTING
70 const Time::Timestamp deadline = Time::getTicks() + 2 * Time::Multiplier::Second;
71 while (context->process->getKernelTime() == context->kernelBefore &&
72 Time::getTicks() < deadline) {
74 }
75#else
77#endif
78 context->firstSliceAccounted = context->process->getKernelTime() > context->kernelBefore;
79 context->ran = 1;
80 return 0;
81}
82
83} // namespace
84
85bool runHostedAccountingRegressions() {
86 Thread* driver = Processor::information().getCurrentThread();
87 const bool interruptsWereEnabled = Processor::getInterrupts();
89 const Time::Timestamp threadUser = driver->getUserTime();
90 const Time::Timestamp threadKernel = driver->getKernelTime();
91 const Time::Timestamp processUser = driver->getParent()->getUserTime();
92 const Time::Timestamp processKernel = driver->getParent()->getKernelTime();
93 driver->publishTimeAccountingForHostedTest(13, 7);
94 driver->publishTimeAccountingForHostedTest(0, 0);
95 const bool exactThreadPublication = !Processor::getInterrupts() &&
96 driver->getUserTime() == threadUser + 13 &&
97 driver->getKernelTime() == threadKernel + 7 &&
98 driver->getParent()->getUserTime() == processUser + 13 &&
99 driver->getParent()->getKernelTime() == processKernel + 7;
100 bool sampledTickAccounting = true;
101#if PEDIGREE_TIME_ACCOUNTING && PEDIGREE_SAMPLED_TIME_ACCOUNTING
102 const CpuTimeMode originalMode = driver->currentTimeAccountingMode();
103 driver->recordTime(CpuTimeMode::User);
104 driver->trackTime(CpuTimeMode::User);
105 driver->transitionTime(CpuTimeMode::User, CpuTimeMode::Kernel, true);
106 sampledTickAccounting &= driver->currentTimeAccountingMode() == CpuTimeMode::Kernel;
107 driver->trackTime(CpuTimeMode::Kernel);
108 driver->transitionTimeAtInterruptReturn(CpuTimeMode::Kernel, CpuTimeMode::User);
109 sampledTickAccounting &=
110 !Processor::getInterrupts() && driver->currentTimeAccountingMode() == CpuTimeMode::User &&
111 driver->getUserTime() == threadUser + 13 && driver->getKernelTime() == threadKernel + 7 &&
112 driver->getParent()->getUserTime() == processUser + 13 &&
113 driver->getParent()->getKernelTime() == processKernel + 7;
114
115 driver->recordTime(CpuTimeMode::Kernel);
116 driver->accountTimerTick(17, false);
117 sampledTickAccounting &= driver->currentTimeAccountingMode() == CpuTimeMode::Kernel &&
118 driver->getUserTime() == threadUser + 30 &&
119 driver->getKernelTime() == threadKernel + 7 &&
120 driver->getParent()->getUserTime() == processUser + 30 &&
121 driver->getParent()->getKernelTime() == processKernel + 7;
122 driver->accountTimerTick(0, false);
123 driver->accountTimerTick(0, true);
124 driver->recordTime(CpuTimeMode::User);
125 driver->accountTimerTick(23, true);
126 sampledTickAccounting &=
127 !Processor::getInterrupts() && driver->currentTimeAccountingMode() == CpuTimeMode::User &&
128 driver->getUserTime() == threadUser + 30 && driver->getKernelTime() == threadKernel + 30 &&
129 driver->getParent()->getUserTime() == processUser + 30 &&
130 driver->getParent()->getKernelTime() == processKernel + 30;
131 driver->recordTime(originalMode);
132#endif
133 Processor::setInterrupts(interruptsWereEnabled);
134
135 const bool loadRequestPassed = Scheduler::instance().runHostedLoadAverageRequestRegression();
136 HostedAccountingProcess* dormant = new HostedAccountingProcess(driver, 0);
137 dormant->publishTimeAccountingForHostedTest(100, 200);
138 bool interestPassed = !dormant->timeAccountingPendingForHostedTest() &&
139 !dormant->timeAccountingInterestForHostedTest() &&
140 dormant->getUserTime() == 100 && dormant->getKernelTime() == 200;
141 dormant->setReportInterest(1, true);
142 for (size_t attempt = 0; !dormant->calls && attempt < 10000; ++attempt) {
143 PerProcessorScheduler::serviceCurrentIrqWorkDoorbellForTest();
145 }
146 interestPassed &=
147 dormant->calls == 1 && dormant->user == 100 && dormant->profile == 300 && !dormant->failures;
148 dormant->setReportInterest(2, true);
149 dormant->setReportInterest(1, false);
150 dormant->publishTimeAccountingForHostedTest(17, 11);
151 interestPassed &= dormant->timeAccountingInterestForHostedTest() == 2;
152 for (size_t attempt = 0; dormant->calls < 2 && attempt < 10000; ++attempt) {
153 PerProcessorScheduler::serviceCurrentIrqWorkDoorbellForTest();
155 }
156 interestPassed &=
157 dormant->calls == 2 && dormant->user == 117 && dormant->profile == 328 && !dormant->failures;
158 dormant->setReportInterest(2, false);
159 dormant->closeTimeAccountingForHostedTest();
160 const size_t dormantCalls = dormant->calls;
161 dormant->publishTimeAccountingForHostedTest(3, 5);
162 interestPassed &= !dormant->timeAccountingInterestForHostedTest() &&
163 !dormant->timeAccountingPendingForHostedTest() &&
164 dormant->calls == dormantCalls && dormant->getUserTime() == 120 &&
165 dormant->getKernelTime() == 216;
166 delete dormant;
167
168 HostedAccountingProcess* process = new HostedAccountingProcess(driver);
169
171 process->publishTimeAccountingForHostedTest(13, 7);
172 Processor::setInterrupts(interruptsWereEnabled);
173
174 constexpr size_t Attempts = 10000;
175 for (size_t attempt = 0; !process->calls && attempt < Attempts; ++attempt) {
176 PerProcessorScheduler::serviceCurrentIrqWorkDoorbellForTest();
178 }
179
180 const bool exactWorkerBatch =
181 process->calls == 1 && process->user == 13 && process->profile == 20 && !process->failures;
182 process->publishTimeAccountingForHostedTest(0, 0);
183 const bool zeroBatchDiscarded = !process->timeAccountingPendingForHostedTest() &&
184 process->getUserTime() == 13 && process->getKernelTime() == 7;
185
186 AccountingThreadContext threadContext(process, process->getKernelTime());
187 Thread* accountedThread =
188 new Thread(process, accountedKernelThread, &threadContext, nullptr, false, true, true);
189 accountedThread->setName("hosted accounting first-slice probe");
190 const bool accountedThreadStarted = accountedThread->start();
191 const bool accountedThreadJoined = accountedThreadStarted && accountedThread->joinForCompletion();
192 if (!accountedThreadStarted) {
193 delete accountedThread;
194 }
195
196 for (size_t attempt = 0; process->profile == 20 && attempt < Attempts; ++attempt) {
197 PerProcessorScheduler::serviceCurrentIrqWorkDoorbellForTest();
199 }
200 const bool firstKernelSliceAccounted = accountedThreadStarted && accountedThreadJoined &&
201 threadContext.ran && threadContext.firstSliceAccounted &&
202 process->user == 13 && process->profile > 20 &&
203 !process->failures;
204 if (!firstKernelSliceAccounted) {
205 ERROR("HOSTED-ACCOUNTING-FIRST: started="
206 << accountedThreadStarted << " joined=" << accountedThreadJoined
207 << " ran=" << static_cast<size_t>(threadContext.ran)
208 << " slice=" << static_cast<size_t>(threadContext.firstSliceAccounted));
209 ERROR("HOSTED-ACCOUNTING-FIRST: user=" << static_cast<size_t>(process->user) << " profile="
210 << static_cast<size_t>(process->profile) << " failures="
211 << static_cast<size_t>(process->failures));
212 }
213
214 process->closeTimeAccountingForHostedTest();
215 const size_t callsBeforeLatePublication = process->calls;
216 const Time::Timestamp userBeforeLatePublication = process->getUserTime();
217 const Time::Timestamp kernelBeforeLatePublication = process->getKernelTime();
218 process->publishTimeAccountingForHostedTest(101, 211);
219 for (size_t attempt = 0; attempt < 32; ++attempt) {
221 }
222 const bool latePublicationDiscarded =
223 process->calls == callsBeforeLatePublication &&
224 !process->timeAccountingPendingForHostedTest() &&
225 process->timeAccountingInterestForHostedTest() &&
226 process->getUserTime() == userBeforeLatePublication + 101 &&
227 process->getKernelTime() == kernelBeforeLatePublication + 211;
228 delete process;
229
230 const bool passed = exactThreadPublication && sampledTickAccounting && loadRequestPassed &&
231 interestPassed && exactWorkerBatch && zeroBatchDiscarded &&
232 firstKernelSliceAccounted && latePublicationDiscarded;
233 if (!passed) {
234 ERROR("HOSTED-WAIT-TEST: FAIL deferred-time-accounting-worker: exact="
235 << exactThreadPublication << " sampled=" << sampledTickAccounting
236 << " load=" << loadRequestPassed << " interest=" << interestPassed
237 << " batch=" << exactWorkerBatch << " zero=" << zeroBatchDiscarded
238 << " first=" << firstKernelSliceAccounted << " late=" << latePublicationDiscarded);
239 } else {
240 NOTICE("HOSTED-WAIT-TEST: PASS deferred-time-accounting-worker");
241 }
242 return passed;
243}
virtual void reportTimesUpdated(Time::Timestamp userTotal, Time::Timestamp total)
Definition Process.h:932
size_t getNumThreads()
Definition Process.cc:1243
void publish()
Definition Process.cc:832
LargeStaticString & description()
Definition Process.h:473
Time::Timestamp getUserTime() const
Definition Process.h:775
void prepareForDestruction()
Definition Process.cc:914
void setTimeAccountingReportInterest(size_t interest, bool enabled)
Definition Process.cc:745
void enableTimeAccountingReports(size_t initialInterest=~size_t(0))
Definition Process.cc:740
static bool getInterrupts()
static ProcessorInformation & information()
static void pause()
static bool inDeviceHardIrq()
Definition Processor.h:559
static void setInterrupts(bool bEnable)
static Scheduler & instance()
Definition Scheduler.h:96
size_t getNumProcesses()
Definition Scheduler.cc:257
void yield()
Definition Scheduler.cc:226
void recordTime(CpuTimeMode mode)
Definition Thread.cc:373
void accountTimerTick(Time::Timestamp delta, bool kernelMode)
Definition Thread.cc:411
CpuTimeMode currentTimeAccountingMode() const
Definition Thread.cc:457
Time::Timestamp getUserTime() const
Definition Thread.h:410
bool joinForCompletion()
Definition Thread.cc:2771
void trackTime(CpuTimeMode mode)
Definition Thread.cc:381
ALWAYS_INLINE void transitionTimeAtInterruptReturn(CpuTimeMode from, CpuTimeMode to)
Definition Thread.h:371
Process * getParent() const
Definition Thread.h:338
bool start()
Definition Thread.cc:794
void transitionTime(CpuTimeMode from, CpuTimeMode to, bool interruptsAlreadyDisabled=false)
Definition Thread.cc:394