The Pedigree Project 0.1
FunctionProfile.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2
3#include "pedigree/kernel/processor/Processor.h"
4#include "pedigree/kernel/processor/hosted/FunctionProfile.h"
5
6#include <errno.h>
7#include <fcntl.h>
8#include <signal.h>
9#include <stdint.h>
10#include <stdio.h>
11#include <stdlib.h>
12#include <time.h>
13#include <unistd.h>
14
15#include <sys/mman.h>
16
17namespace {
18struct Event {
19 uint64_t ticks;
20 uint64_t function;
21 uint64_t caller;
22 uint64_t kind;
23};
24static_assert(sizeof(Event) == 32);
25constexpr size_t Capacity = 2 * 1024 * 1024;
26Event* events = nullptr;
27const char* directory = nullptr;
28size_t limit = 100;
29bool active = false;
30thread_local bool owner = false;
31bool previousInterrupts = false;
32bool openCapture = false;
33size_t eventCount = 0;
34size_t dropped = 0;
35unsigned invalidation = 0;
36const char* phase = nullptr;
37size_t repetition = 0;
38size_t iterations = 0;
39uint64_t startNs = 0;
40
41uint64_t nowNs() {
42 timespec ts = {};
43 clock_gettime(CLOCK_MONOTONIC, &ts);
44 return uint64_t(ts.tv_sec) * 1000000000ULL + ts.tv_nsec;
45}
46
47void record(void* function, void* caller, uint64_t kind) {
48 if (!__atomic_load_n(&active, __ATOMIC_RELAXED) || !owner) {
49 return;
50 }
51 if (eventCount == Capacity) {
52 ++dropped;
53 return;
54 }
55 uint32_t low, high;
56 // TSC ticks measure elapsed time, not CPU cycles. Serialise the read so the
57 // compiler hooks have ordered boundaries even around very small inlines.
58 asm volatile("lfence; rdtsc; lfence" : "=a"(low), "=d"(high) : : "memory");
59 events[eventCount++] = {uint64_t(high) << 32 | low, reinterpret_cast<uintptr_t>(function),
60 reinterpret_cast<uintptr_t>(caller), kind};
61}
62
63bool writeEvents(const char* path) {
64 int fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0600);
65 if (fd < 0) {
66 return false;
67 }
68 const char* data = reinterpret_cast<const char*>(events);
69 size_t remaining = eventCount * sizeof(Event);
70 bool ok = true;
71 while (remaining) {
72 const ssize_t written = write(fd, data, remaining);
73 if (written < 0 && errno == EINTR) {
74 continue;
75 }
76 if (written <= 0) {
77 ok = false;
78 break;
79 }
80 data += written;
81 remaining -= written;
82 }
83 return close(fd) == 0 && ok;
84}
85} // namespace
86
87extern "C" EXPORTED_PUBLIC void __cyg_profile_func_enter(void* function, void* caller) {
88 record(function, caller, 1);
89}
90
91extern "C" EXPORTED_PUBLIC void __cyg_profile_func_exit(void* function, void* caller) {
92 record(function, caller, 2);
93}
94
95bool hostedFunctionProfileInitialise() {
96 directory = getenv("PEDIGREE_HOSTED_FUNCTION_PROFILE_DIR");
97 if (!directory || !*directory) {
98 directory = nullptr;
99 return true;
100 }
101 const char* value = getenv("PEDIGREE_HOSTED_FUNCTION_PROFILE_LIMIT");
102 if (value) {
103 char* end = nullptr;
104 const unsigned long requested = strtoul(value, &end, 10);
105 if (!*value || *end || !requested || requested > 10000) {
106 fprintf(stderr, "Function profile limit must be between 1 and 10000.\n");
107 return false;
108 }
109 limit = requested;
110 }
111 void* storage = mmap(nullptr, Capacity * sizeof(Event), PROT_READ | PROT_WRITE,
112 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
113 if (storage == MAP_FAILED) {
114 perror("Function profile buffer");
115 return false;
116 }
117 events = static_cast<Event*>(storage);
118 // Fault in storage before a capture. Hooks must never allocate or fault in a
119 // Pedigree page while reconstructing another function's entry or exit.
120 volatile char* bytes = static_cast<volatile char*>(storage);
121 for (size_t offset = 0; offset < Capacity * sizeof(Event); offset += 4096) {
122 bytes[offset] = 0;
123 }
124 return true;
125}
126
127size_t hostedFunctionProfileCount(size_t count) {
128 return directory && count > limit ? limit : count;
129}
130
131void hostedFunctionProfileBegin(const char* name, size_t rep, size_t count) {
132 if (!directory) {
133 return;
134 }
135 previousInterrupts = Processor::getInterrupts();
137 owner = true;
138 phase = name;
139 repetition = rep + 1;
140 iterations = count;
141 eventCount = dropped = invalidation = 0;
142 openCapture = true;
143 startNs = nowNs();
144 __atomic_store_n(&active, true, __ATOMIC_RELEASE);
145}
146
147void hostedFunctionProfileInvalidate(HostedProfileInvalidation reason) {
148 if (__atomic_load_n(&active, __ATOMIC_RELAXED) && owner) {
149 invalidation = static_cast<unsigned>(reason);
150 // A suspended stack must not make another thread look like its child.
151 __atomic_store_n(&active, false, __ATOMIC_RELEASE);
152 }
153}
154
155bool hostedFunctionProfileEnd() {
156 if (!directory || !openCapture) {
157 return true;
158 }
159 __atomic_store_n(&active, false, __ATOMIC_RELEASE);
160 const uint64_t elapsed = nowNs() - startNs;
161 owner = false;
162 openCapture = false;
163 Processor::setInterrupts(previousInterrupts);
164
165 char path[4096];
166 int length = snprintf(path, sizeof(path), "%s/%s-%zu.bin", directory, phase, repetition);
167 bool saved = length > 0 && size_t(length) < sizeof(path) && writeEvents(path);
168 length = snprintf(path, sizeof(path), "%s/%s-%zu.json", directory, phase, repetition);
169 FILE* metadata = length > 0 && size_t(length) < sizeof(path) ? fopen(path, "wx") : nullptr;
170 if (metadata) {
171 const int written =
172 fprintf(metadata,
173 "{\"format_version\":1,\"phase\":\"%s\",\"repetition\":%zu,\"count\":%zu,"
174 "\"events\":%zu,\"dropped\":%zu,\"invalidation\":%u,\"elapsed_ns\":%llu}\n",
175 phase, repetition, iterations, eventCount, dropped, invalidation,
176 static_cast<unsigned long long>(elapsed));
177 const int closed = fclose(metadata);
178 saved = saved && written > 0 && closed == 0;
179 } else {
180 saved = false;
181 }
182 fprintf(stderr,
183 "HOSTED-FUNCTION-PROFILE: %s phase=%s rep=%zu count=%zu events=%zu "
184 "dropped=%zu invalidation=%u\n",
185 saved && !dropped && !invalidation ? "PASS" : "FAIL", phase, repetition, iterations,
186 eventCount, dropped, invalidation);
187 return saved && !dropped && !invalidation;
188}
Definition Event.h:49
static bool getInterrupts()
static void setInterrupts(bool bEnable)