The Pedigree Project 0.1
LatencyAccounting.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/LatencyAccounting.h"
3
4#if PEDIGREE_LATENCY_ACCOUNTING
5#include "pedigree/kernel/process/CpuAffinity.h"
6#include "pedigree/kernel/processor/Processor.h"
7#include "pedigree/kernel/processor/ProcessorInformation.h"
8#include "pedigree/kernel/time/Time.h"
9
10namespace LatencyAccounting {
11namespace {
12struct alignas(64) CpuCounters {
13 uint64_t sequence = 0;
14 Snapshot data;
15};
16CpuCounters counters[CpuAffinityMask::MaximumCpus];
17bool clockReady = false;
18
19CpuCounters* local(bool includeNmi = false) {
20 if (!__atomic_load_n(&clockReady, __ATOMIC_ACQUIRE)) {
21 return nullptr;
22 }
23 const size_t cpu = Processor::index();
24 if (cpu >= CpuAffinityMask::MaximumCpus ||
25 !__atomic_load_n(&counters[cpu].data.values[OnlineSince], __ATOMIC_RELAXED) ||
26 (!includeNmi && Processor::information().kernelGsAnchor()->latencyNmiDepth)) {
27 return nullptr;
28 }
29 return &counters[cpu];
30}
31
32void begin(CpuCounters& cpu) {
33 // x64 store ordering and the local IRQ-off writer need no locked RMW.
34 __atomic_store_n(&cpu.sequence, cpu.sequence + 1, __ATOMIC_RELAXED);
35 __atomic_thread_fence(__ATOMIC_RELEASE);
36}
37void end(CpuCounters& cpu) {
38 __atomic_store_n(&cpu.sequence, cpu.sequence + 1, __ATOMIC_RELEASE);
39}
40uint64_t get(CpuCounters& cpu, Field field) {
41 return __atomic_load_n(&cpu.data.values[field], __ATOMIC_RELAXED);
42}
43void set(CpuCounters& cpu, Field field, uint64_t value) {
44 __atomic_store_n(&cpu.data.values[field], value, __ATOMIC_RELAXED);
45}
46void add(CpuCounters& cpu, Field field, uint64_t value) {
47 set(cpu, field, get(cpu, field) + value);
48}
49} // namespace
50
51void armCpu() {
52 const size_t index = Processor::index();
53 if (index >= CpuAffinityMask::MaximumCpus) {
54 return;
55 }
56 CpuCounters& cpu = counters[index];
57 const uint64_t now = Time::sampleCpuTime().timestamp;
58 begin(cpu);
59 set(cpu, OnlineSince, now ? now : 1);
60 set(cpu, IrqOffOpenSince, now ? now : 1);
61 end(cpu);
62 __atomic_store_n(&clockReady, true, __ATOMIC_RELEASE);
63}
64
65bool active() {
66 return local() != nullptr;
67}
68
69void recordIrqState(bool enabled, uintptr_t site) {
70 CpuCounters* cpu = local();
71 if (!cpu) {
72 return;
73 }
74 const uint64_t start = get(*cpu, IrqOffOpenSince);
75 if (enabled != (start != 0)) {
76 return;
77 }
78 const uint64_t now = Time::sampleCpuTime().timestamp;
79 begin(*cpu);
80 if (enabled) {
81 const uint64_t elapsed = now >= start ? now - start : 0;
82 add(*cpu, IrqOffNanoseconds, elapsed);
83 add(*cpu, IrqOffCount, 1);
84 if (elapsed > get(*cpu, IrqOffMax)) {
85 set(*cpu, IrqOffMax, elapsed);
86 set(*cpu, IrqOffMaxEnd, now);
87 set(*cpu, IrqOffMaxSite, get(*cpu, IrqOffOpenSite));
88 }
89 if (elapsed >= Time::Multiplier::Millisecond) {
90 add(*cpu, IrqOffOver1ms, 1);
91 }
92 if (elapsed >= 10 * Time::Multiplier::Millisecond) {
93 add(*cpu, IrqOffOver10ms, 1);
94 }
95 set(*cpu, IrqOffOpenSince, 0);
96 set(*cpu, IrqOffOpenSite, 0);
97 } else {
98 set(*cpu, IrqOffOpenSince, now ? now : 1);
99 set(*cpu, IrqOffOpenSite, site);
100 }
101 end(*cpu);
102}
103
104void recordDeferredCpu(uint64_t elapsed) {
105 if (CpuCounters* cpu = local(true)) {
106 // Interrupt accounting publishes the preceding CPU slice after NMI entry.
107 // This independent counter must accept that slice without nesting a writer
108 // inside an interrupted IRQ-state transaction.
109 __atomic_fetch_add(&cpu->data.values[DeferredCpuNanoseconds], elapsed, __ATOMIC_RELAXED);
110 }
111}
112
113void recordDeferredWall(uint64_t elapsed) {
114 if (CpuCounters* cpu = local()) {
115 begin(*cpu);
116 add(*cpu, DeferredWallNanoseconds, elapsed);
117 add(*cpu, DeferredCount, 1);
118 if (elapsed > get(*cpu, DeferredMax)) {
119 set(*cpu, DeferredMax, elapsed);
120 }
121 end(*cpu);
122 }
123}
124
125bool snapshot(size_t index, Snapshot& result) {
126 if (index >= CpuAffinityMask::MaximumCpus) {
127 return false;
128 }
129 CpuCounters& cpu = counters[index];
130 // A busy remote writer must not keep a diagnostic reader spinning forever.
131 for (size_t attempt = 0; attempt < 32; ++attempt) {
132 const uint64_t before = __atomic_load_n(&cpu.sequence, __ATOMIC_ACQUIRE);
133 if (before & 1) {
134 continue;
135 }
136 for (size_t field = 0; field < Count; ++field) {
137 result.values[field] = __atomic_load_n(&cpu.data.values[field], __ATOMIC_RELAXED);
138 }
139 __atomic_thread_fence(__ATOMIC_ACQUIRE);
140 if (before == __atomic_load_n(&cpu.sequence, __ATOMIC_RELAXED)) {
141 return result.values[OnlineSince] != 0;
142 }
143 }
144 return false;
145}
146} // namespace LatencyAccounting
147
148extern "C" void pedigree_irq_time_state(bool enabled) {
149 LatencyAccounting::recordIrqState(enabled,
150 reinterpret_cast<uintptr_t>(__builtin_return_address(0)));
151}
152#endif
static ProcessorInformation & information()
static size_t index()