The Pedigree Project 0.1
PerCpuTimeAccounting.h
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.
6 */
7#ifndef PEDIGREE_KERNEL_PROCESS_PERCPUTIMEACCOUNTING_H
8#define PEDIGREE_KERNEL_PROCESS_PERCPUTIMEACCOUNTING_H
9
10#include "pedigree/kernel/process/DeferredTimeAccounting.h"
11#include "pedigree/kernel/utilities/new"
12
14 public:
15 explicit PerCpuTimeAccounting(size_t count) : m_Storage(nullptr), m_Slots(nullptr), m_Count(0) {
16 if (!count || count > (~size_t(0) - (SlotSize - 1)) / SlotSize) {
17 return;
18 }
19 const size_t bytes = count * SlotSize + SlotSize - 1;
20#if UTILITY_LINUX
21 m_Storage = new (std::nothrow) uint8_t[bytes];
22#else
23 m_Storage = new uint8_t[bytes];
24#endif
25 if (!m_Storage) {
26 return;
27 }
28 // The kernel's over-aligned operator new does not implement alignment.
29 const uintptr_t aligned =
30 (reinterpret_cast<uintptr_t>(m_Storage) + SlotSize - 1) & ~uintptr_t(SlotSize - 1);
31 m_Slots = reinterpret_cast<uint8_t*>(aligned);
32 m_Count = count;
33 for (size_t cpu = 0; cpu < m_Count; ++cpu) {
34 new (slot(cpu)) Slot{};
35 }
36 }
37
39 delete[] m_Storage;
40 }
41
43 ALWAYS_INLINE bool add(CpuTimeMode mode, Time::Timestamp elapsed, size_t cpu) {
44 if (cpu >= m_Count) {
45 return false;
46 }
47 Slot* current = slot(cpu);
48 Time::Timestamp* value = mode == CpuTimeMode::User ? &current->user : &current->kernel;
49#if X64 && !HOSTED && !UTILITY_LINUX
50 // Only this CPU writes its slot. A single instruction also excludes an
51 // NMI interleaving the read and write of a split load/add/store sequence.
52 asm volatile("addq %1, %0" : "+m"(*value) : "r"(elapsed) : "cc");
53#else
54 __atomic_fetch_add(value, elapsed, __ATOMIC_RELAXED);
55#endif
56 return true;
57 }
58
59 Time::Timestamp total(CpuTimeMode mode) const {
60 Time::Timestamp result = 0;
61 for (size_t cpu = 0; cpu < m_Count; ++cpu) {
62 const Slot* current = slot(cpu);
63 const Time::Timestamp* value = mode == CpuTimeMode::User ? &current->user : &current->kernel;
64 result += __atomic_load_n(value, __ATOMIC_ACQUIRE);
65 }
66 return result;
67 }
68
69 private:
70 static constexpr size_t SlotSize = 64;
71 struct alignas(SlotSize) Slot {
72 Time::Timestamp user;
73 Time::Timestamp kernel;
74 uint8_t padding[SlotSize - 2 * sizeof(Time::Timestamp)];
75 };
76 static_assert(sizeof(Slot) == SlotSize && alignof(Slot) == SlotSize,
77 "CPU accounting slots must occupy separate aligned cache lines");
78
79 ALWAYS_INLINE Slot* slot(size_t cpu) const {
80 return reinterpret_cast<Slot*>(m_Slots + cpu * SlotSize);
81 }
82
83 uint8_t* m_Storage;
84 uint8_t* m_Slots;
85 size_t m_Count;
86
88 PerCpuTimeAccounting& operator=(const PerCpuTimeAccounting&) = delete;
89};
90
91#endif
ALWAYS_INLINE bool add(CpuTimeMode mode, Time::Timestamp elapsed, size_t cpu)