The Pedigree Project 0.1
ProcFs-status.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/LatencyAccounting.h"
3#include "pedigree/kernel/Metrics.h"
4#include "pedigree/kernel/machine/Disk.h"
5#include "pedigree/kernel/process/PerProcessorScheduler.h"
6#include "pedigree/kernel/process/Scheduler.h"
7#include "pedigree/kernel/process/Thread.h"
8#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
9#include "pedigree/kernel/processor/Processor.h"
10#include "pedigree/kernel/syscallError.h"
11#include "pedigree/kernel/time/Time.h"
12#include "pedigree/kernel/utilities/StaticString.h"
13
14#include "DevFs-block.h"
15#include "PosixProcess.h"
16#include "PosixSubsystem.h"
17#include "ProcFs.h"
18#include "file-metadata.h"
19#include "metadata-abi.h"
20#include "modules/system/vfs/MountView.h"
21#include "modules/system/vfs/VFS.h"
22#include <sys/stat.h>
23
24namespace {
25constexpr uint64_t ClockTicksPerSecond = 100;
26constexpr uint64_t NanosecondsPerClockTick = Time::Multiplier::Second / ClockTicksPerSecond;
27
28struct TaskCounts {
29 size_t processes = 0;
30 size_t total = 0;
31 size_t runnable = 0;
32 size_t lastPid = 0;
33};
34
35TaskCounts taskCounts();
36
37class GeneratedFile : public File {
38 public:
39 GeneratedFile(const String& name, uintptr_t inode, ProcFs& filesystem, File* parent)
40 : File(name, 0, 0, 0, inode, &filesystem, 0, parent) {
41 setPermissionsOnly(FILE_UR | FILE_GR | FILE_OR);
42 setUidOnly(0);
43 setGidOnly(0);
44 }
45
46 uint64_t readBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool) override {
47 String contents;
48 if (!generate(contents) || location >= contents.length())
49 return 0;
50 if (size > contents.length() - location)
51 size = contents.length() - location;
52 MemoryCopy(reinterpret_cast<void*>(buffer), contents.cstr() + location, size);
53 return size;
54 }
55
56 uint64_t writeBytewise(uint64_t, uint64_t, uintptr_t, bool) override {
57 return 0;
58 }
59
60 size_t getSize() override {
61 String contents;
62 return generate(contents) ? contents.length() : 0;
63 }
64
65 protected:
66 bool isBytewise() const override {
67 return true;
68 }
69 virtual bool generate(String& contents) const = 0;
70};
71
72class MetricsSnapshotFile final : public File {
73 public:
74 MetricsSnapshotFile(uintptr_t inode, ProcFs& filesystem, File* parent, String&& contents)
75 : File(String("metrics"), 0, 0, 0, inode, &filesystem, contents.length(), parent),
76 m_Contents(pedigree_std::move(contents)) {
77 setPermissionsOnly(FILE_UR | FILE_GR | FILE_OR);
78 setUidOnly(0);
79 setGidOnly(0);
80 }
81
82 uint64_t readBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool) override {
83 if (location >= m_Contents.length()) {
84 return 0;
85 }
86 size = min(size, uint64_t(m_Contents.length() - location));
87 MemoryCopy(reinterpret_cast<void*>(buffer), m_Contents.cstr() + location, size);
88 return size;
89 }
90
91 uint64_t writeBytewise(uint64_t, uint64_t, uintptr_t, bool) override {
92 SYSCALL_ERROR(PermissionDenied);
93 return 0;
94 }
95
96 protected:
97 bool isBytewise() const override {
98 return true;
99 }
100
101 private:
102 const String m_Contents;
103};
104
105class MetricsFile final : public File {
106 public:
107 MetricsFile(uintptr_t inode, ProcFs& filesystem, File* parent)
108 : File(String("metrics"), 0, 0, 0, inode, &filesystem, 0, parent) {
109 setPermissionsOnly(FILE_UR | FILE_GR | FILE_OR);
110 setUidOnly(0);
111 setGidOnly(0);
112 }
113
114 File* openForDescriptor(RetainedFile& owner) override {
115 const size_t cpus = Processor::getCount();
116#if PEDIGREE_METRICS
117 auto snapshots = UniqueArray<Metrics::Snapshot>::allocate(cpus);
118 if (!snapshots) {
119 SYSCALL_ERROR(OutOfMemory);
120 return nullptr;
121 }
122 // Capture every counter before formatting, without stopping other CPUs.
123 for (size_t cpu = 0; cpu < cpus; ++cpu) {
124 Metrics::snapshot(cpu, snapshots.get()[cpu]);
125 }
126#endif
127 const uint64_t uptime = Time::getTicks();
128 StaticString<32> uptimeText;
129 uptimeText.append(uptime / Time::Multiplier::Second);
130 uptimeText.append(".");
131 uptimeText.append(uptime % Time::Multiplier::Second, 10, 9);
132 String contents;
133 contents.Format(
134 "# TYPE pedigree_metrics_enabled gauge\npedigree_metrics_enabled %u\n"
135 "# TYPE pedigree_cpus gauge\npedigree_cpus %lu\n"
136 "# TYPE pedigree_uptime_seconds gauge\npedigree_uptime_seconds %s\n",
137 unsigned(PEDIGREE_METRICS), cpus, static_cast<const char*>(uptimeText));
138 const auto memory = PhysicalMemoryManager::instance().memorySnapshot();
139 const TaskCounts tasks = taskCounts();
140 String line;
141 // String::Format has a 256-byte buffer, so format each gauge separately.
142 line.Format(
143 "# HELP pedigree_memory_managed_bytes Physical memory managed by the page allocator.\n"
144 "# TYPE pedigree_memory_managed_bytes gauge\npedigree_memory_managed_bytes %lu\n",
145 memory.totalPages * PhysicalMemoryManager::getPageSize());
146 contents += line;
147 line.Format(
148 "# HELP pedigree_memory_free_bytes Physical memory currently free in the page allocator.\n"
149 "# TYPE pedigree_memory_free_bytes gauge\npedigree_memory_free_bytes %lu\n",
150 memory.freePages * PhysicalMemoryManager::getPageSize());
151 contents += line;
152 line.Format(
153 "# HELP pedigree_processes Processes registered with the scheduler.\n"
154 "# TYPE pedigree_processes gauge\npedigree_processes %lu\n",
155 tasks.processes);
156 contents += line;
157 line.Format(
158 "# HELP pedigree_threads Non-idle threads registered with a process.\n"
159 "# TYPE pedigree_threads gauge\npedigree_threads %lu\n",
160 tasks.total);
161 contents += line;
162 line.Format(
163 "# HELP pedigree_runnable_threads Non-idle threads ready or running.\n"
164 "# TYPE pedigree_runnable_threads gauge\npedigree_runnable_threads %lu\n",
165 tasks.runnable);
166 contents += line;
167#if PEDIGREE_METRICS
168 struct Counter {
169 const char* name;
170 Metrics::Counter counter;
171 const char* help;
172 };
173 static const Counter counters[] = {
174 {"pedigree_scheduler_schedules_total", Metrics::Schedule, "Scheduler selection calls."},
175 {"pedigree_scheduler_yields_total", Metrics::Yield, "Explicit scheduler yield calls."},
176 {"pedigree_scheduler_context_switches_total", Metrics::ContextSwitch,
177 "Context switches to another thread."},
178 {"pedigree_scheduler_same_thread_selections_total", Metrics::SameThread,
179 "Scheduler selections that kept the current thread."},
180 {"pedigree_scheduler_idle_selections_total", Metrics::IdleSelection,
181 "Context switches selecting an idle thread."},
182 {"pedigree_scheduler_timer_callbacks_total", Metrics::Timer, "Scheduler timer callbacks."},
183 {"pedigree_scheduler_reschedule_services_total", Metrics::RescheduleService,
184 "Pending scheduling and IRQ work service calls, including calls without pending work."},
185 {"pedigree_scheduler_worker_wakeups_total", Metrics::WorkerWake,
186 "Successful IRQ worker wakeups."},
187#if X64
188 {"pedigree_interrupt_entries_total", Metrics::Interrupt,
189 "x86-64 interrupt handler entries for vectors 32 and above."},
190 {"pedigree_exception_entries_total", Metrics::Exception,
191 "x86-64 exception handler entries for vectors below 32."},
192 {"pedigree_syscalls_total", Metrics::Syscall, "x86-64 C++ syscall dispatch entries."},
193#endif
194 {"pedigree_process_created_total", Metrics::ProcessCreated,
195 "Completed process constructions."},
196 {"pedigree_process_exited_total", Metrics::ProcessExited,
197 "Processes published as terminated."},
198 {"pedigree_process_destroyed_total", Metrics::ProcessDestroyed,
199 "Completed process destructions."},
200 {"pedigree_thread_created_total", Metrics::ThreadCreated,
201 "Threads registered with a process, including idle threads."},
202 {"pedigree_thread_exit_started_total", Metrics::ThreadExitStarted,
203 "Threads beginning shutdown for the first time."},
204 {"pedigree_thread_reapable_total", Metrics::ThreadReapable,
205 "Threads retired from their execution stack."},
206 {"pedigree_thread_destroyed_total", Metrics::ThreadDestroyed,
207 "Completed thread destructions."},
208 {"pedigree_wait_queue_waits_total", Metrics::WaitQueueWait,
209 "Waiter enrollments in wait queues."},
210 {"pedigree_wait_queue_unqueues_total", Metrics::WaitQueueUnqueue,
211 "Waiter removals from wait queues."},
212 {"pedigree_wait_queue_wakes_total", Metrics::WaitQueueWake,
213 "First successful completion of an enrolled waiter."},
214 {"pedigree_wait_queue_early_wakes_total", Metrics::WaitQueueEarlyWake,
215 "Waiter completions before the thread entered sleeping state."},
216 {"pedigree_wait_queue_cancels_total", Metrics::WaitQueueCancel,
217 "Enrolled waiters removed by cancellation."},
218 {"pedigree_wait_queue_requeues_total", Metrics::WaitQueueRequeue,
219 "Waiter moves to another channel."},
220 {"pedigree_semaphore_contended_acquires_total", Metrics::SemaphoreContended,
221 "Semaphore acquires that missed the initial fast attempts."},
222 {"pedigree_physical_page_allocations_total", Metrics::PhysicalPageAlloc,
223 "Successful single-page allocation calls after CPU initialization."},
224 {"pedigree_physical_page_allocation_failures_total", Metrics::PhysicalPageAllocFailure,
225 "Failed single-page allocation calls after CPU initialization."},
226 {"pedigree_physical_page_frees_total", Metrics::PhysicalPageFree,
227 "Pages returned to the allocator after their last reference."},
228 {"pedigree_memory_pressure_passes_total", Metrics::MemoryPressurePass,
229 "Admitted memory pressure compact passes."},
230 {"pedigree_memory_pressure_successes_total", Metrics::MemoryPressurePassSuccess,
231 "Memory pressure passes with a handler reporting success."},
232 {"pedigree_memory_pressure_kill_requests_total", Metrics::MemoryPressureKill,
233 "Victim kill calls made under memory pressure."},
234 {"pedigree_page_fault_entries_total", Metrics::PageFault,
235 "Architecture page-fault handler entries."},
236 {"pedigree_page_fault_copy_on_write_total", Metrics::PageFaultCopyOnWrite,
237 "Page faults resolved by copy-on-write."},
238 {"pedigree_page_fault_handled_total", Metrics::PageFaultHandled,
239 "Page faults resolved by a registered trap handler."},
240 {"pedigree_page_fault_deferred_total", Metrics::PageFaultDeferred,
241 "Page faults deferred to a userspace subsystem."},
242 {"pedigree_cache_lookup_hits_total", Metrics::CacheLookupHit,
243 "Successful Cache::lookup calls."},
244 {"pedigree_cache_lookup_misses_total", Metrics::CacheLookupMiss,
245 "Cache::lookup calls without an available page."},
246 {"pedigree_cache_read_bytes_total", Metrics::CacheReadBytes,
247 "Bytes returned by Cache::read."},
248 {"pedigree_cache_evicted_pages_total", Metrics::CacheEvictedPages,
249 "Cache pages successfully retired."},
250 {"pedigree_cache_writeback_pages_total", Metrics::CacheWritebackPages,
251 "Cache pages submitted to backing-store writeback callbacks."},
252 {"pedigree_cache_writeback_failures_total", Metrics::CacheWritebackFailures,
253 "Cache pages whose backing-store writeback callback failed."},
254 {"pedigree_network_rx_packets_total", Metrics::NetworkRxAccepted,
255 "Received packets accepted by the network stack."},
256 {"pedigree_network_rx_bytes_total", Metrics::NetworkRxBytes,
257 "Received bytes accepted by the network stack."},
258 {"pedigree_network_rx_no_device_total", Metrics::NetworkRxNoDevice,
259 "Received packets dropped without a registered device."},
260 {"pedigree_network_rx_filtered_total", Metrics::NetworkRxFiltered,
261 "Received packets rejected by the network filter."},
262 {"pedigree_network_rx_no_buffer_total", Metrics::NetworkRxNoBuffer,
263 "Received packets dropped for lack of a network buffer."},
264 {"pedigree_network_rx_input_failures_total", Metrics::NetworkRxInputFailed,
265 "Received packets rejected by the network stack input."},
266 {"pedigree_network_tx_packets_total", Metrics::NetworkTxAccepted,
267 "Transmitted packets accepted by a device send call."},
268 {"pedigree_network_tx_bytes_total", Metrics::NetworkTxBytes,
269 "Transmitted bytes accepted by a device send call."},
270 {"pedigree_network_tx_filtered_total", Metrics::NetworkTxFiltered,
271 "Transmitted packets rejected by the network filter."},
272 {"pedigree_network_tx_send_failures_total", Metrics::NetworkTxSendFailed,
273 "Transmitted packets rejected by a device send call."},
274 {"pedigree_file_read_calls_total", Metrics::FileReadCalls,
275 "File::read calls, including cached reads through that path."},
276 {"pedigree_file_cached_read_calls_total", Metrics::FileCachedReadCalls,
277 "Direct File::readCached calls."},
278 {"pedigree_file_read_bytes_total", Metrics::FileReadBytes,
279 "Bytes returned by file read calls, counted once per path."},
280 {"pedigree_file_write_calls_total", Metrics::FileWriteCalls,
281 "File write and append calls."},
282 {"pedigree_file_write_bytes_total", Metrics::FileWriteBytes,
283 "Bytes returned by file write and append calls."},
284 {"pedigree_vfs_find_calls_total", Metrics::VfsFindCalls, "VFS::find calls."},
285 {"pedigree_vfs_find_misses_total", Metrics::VfsFindMisses,
286 "VFS::find calls returning no file."},
287 {"pedigree_vfs_sync_calls_total", Metrics::VfsSyncCalls,
288 "Filesystem backend sync invocations."},
289 {"pedigree_vfs_sync_unsupported_total", Metrics::VfsSyncUnsupported,
290 "Filesystem backend sync invocations reporting unsupported."},
291 {"pedigree_vfs_sync_failures_total", Metrics::VfsSyncFailed,
292 "Filesystem backend sync invocations that failed for another reason."},
293 };
294 for (const Counter& counter : counters) {
295 line.Format("# HELP %s %s\n# TYPE %s counter\n", counter.name, counter.help, counter.name);
296 contents += line;
297 for (size_t cpu = 0; cpu < cpus; ++cpu) {
298 StaticString<32> value;
299 value.append(snapshots.get()[cpu].values[counter.counter]);
300 line.Format("%s{cpu=\"%lu\"} %s\n", counter.name, cpu, static_cast<const char*>(value));
301 contents += line;
302 }
303 }
304 for (unsigned contended = 0; contended < 2; ++contended) {
305 const char* name = contended ? "pedigree_spinlock_contended_acquires_total"
306 : "pedigree_spinlock_acquires_total";
307 const char* help = contended
308 ? "Successful acquisitions that paused at least once while waiting."
309 : "Successful acquisition calls, including recursive acquisitions.";
310 line.Format("# HELP %s %s\n# TYPE %s counter\n", name, help, name);
311 contents += line;
312 for (size_t cpu = 0; cpu < cpus; ++cpu) {
313 const auto counter = contended ? Metrics::SpinlockContended : Metrics::SpinlockAcquire;
314 StaticString<32> value;
315 value.append(snapshots.get()[cpu].values[counter]);
316 line.Format("%s{cpu=\"%lu\",policy=\"no_irq\"} %s\n", name, cpu,
317 static_cast<const char*>(value));
318 contents += line;
319 }
320 }
321#endif
322 contents += String("# EOF\n");
323 auto* snapshot = new MetricsSnapshotFile(getInode(), *static_cast<ProcFs*>(getFilesystem()),
324 getParent(), pedigree_std::move(contents));
325 if (!snapshot || !VFS::instance().tryTrackFile(snapshot)) {
326 delete snapshot;
327 SYSCALL_ERROR(OutOfMemory);
328 return nullptr;
329 }
330 owner.adopt(snapshot);
331 return snapshot;
332 }
333};
334
335TaskCounts taskCounts() {
336 TaskCounts result;
337 Scheduler& scheduler = Scheduler::instance();
338 const size_t processCount = scheduler.getNumProcesses();
339 result.processes = processCount;
340 for (size_t index = 0; index < processCount; ++index) {
342 if (!scheduler.acquireProcess(process, index)) {
343 continue;
344 }
345 if (process->getType() == Process::Posix && process->getUserspaceId() > result.lastPid) {
346 result.lastPid = process->getUserspaceId();
347 }
348 const size_t threadCount = process->getNumThreads();
349 for (size_t threadIndex = 0; threadIndex < threadCount; ++threadIndex) {
351 if (!process->acquireThread(thread, threadIndex)) {
352 continue;
353 }
354 const auto* owner = thread->getScheduler();
355 if (owner && owner->isIdleThread(thread.get())) {
356 continue;
357 }
358 ++result.total;
359 const Thread::Status status = thread->getStatus();
360 if (status == Thread::Ready || status == Thread::Running) {
361 ++result.runnable;
362 }
363 }
364 }
365 return result;
366}
367
368class KernelThreadsFile final : public GeneratedFile {
369 public:
370 KernelThreadsFile(uintptr_t inode, ProcFs& filesystem, File* parent)
371 : GeneratedFile(String("threads"), inode, filesystem, parent) {}
372
373 private:
374 bool generate(String& contents) const override {
375 // These are internal process/task identities, not the separate POSIX PID namespace.
376 contents = String("process_id task_id cpu state priority user_ns system_ns idle name\n");
377 Scheduler& scheduler = Scheduler::instance();
378 const size_t processCount = scheduler.getNumProcesses();
379 for (size_t index = 0; index < processCount; ++index) {
381 if (!scheduler.acquireProcess(process, index) || process->getType() == Process::Posix) {
382 continue;
383 }
384 const size_t threadCount = process->getNumThreads();
385 for (size_t threadIndex = 0; threadIndex < threadCount; ++threadIndex) {
387 if (!process->acquireThread(thread, threadIndex)) {
388 continue;
389 }
390 const auto* owner = thread->getScheduler();
391 const auto status = thread->getStatus();
392 const char state = status == Thread::Zombie ? 'Z'
393 : status == Thread::Ready || status == Thread::Running ? 'R'
394 : 'S';
395 // Keep one line per thread even when a diagnostic name contains whitespace.
396 char name[128];
397 const String& description = thread->getName();
398 const size_t length = min(description.length(), sizeof(name) - 1);
399 for (size_t i = 0; i < length; ++i) {
400 const char value = description[i];
401 name[i] = value < ' ' || value == 127 ? ' ' : value;
402 }
403 name[length] = 0;
404 String line;
405 // Advancing counters must not shift later offsets in a partial read.
406 line.Format("%20lu %20lu %20lu %c %20lu %20lu %20lu %u ", process->getId(),
407 thread->getTaskId(), owner ? owner->logicalCpu() : 0, state,
408 thread->getPriority(), thread->getUserTime(), thread->getKernelTime(),
409 owner && owner->isIdleThread(thread.get()) ? 1U : 0U);
410 contents += line;
411 contents += String(length ? name : "unnamed");
412 contents += String("\n");
413 }
414 }
415 return true;
416 }
417};
418
419class LatencyFile final : public GeneratedFile {
420 public:
421 LatencyFile(uintptr_t inode, ProcFs& filesystem, File* parent)
422 : GeneratedFile(String("latency"), inode, filesystem, parent) {
423 // Origin addresses are useful for symbolizing a long IRQ-off interval.
424 setPermissionsOnly(FILE_UR);
425 }
426
427 private:
428 bool generate(String& contents) const override {
429 contents.Format("enabled %u\n", unsigned(PEDIGREE_LATENCY_ACCOUNTING));
430 contents += String(
431 "cpu sample_ns valid online_since_ns irq_off_ns irq_off_count irq_off_max_ns "
432 "irq_off_max_end_ns irq_off_max_site irq_off_ge_1ms irq_off_ge_10ms "
433 "irq_off_open_since_ns irq_off_open_site event_deferred_cpu_ns "
434 "event_deferred_thread_ns event_deferred_count event_deferred_max_ns\n");
435 for (size_t cpu = 0; cpu < Processor::getCount(); ++cpu) {
437 const bool valid = LatencyAccounting::snapshot(cpu, snapshot);
438 String line;
439 line.Format("%20lu %20lu %u", cpu, Time::getTicks(), unsigned(valid));
440 contents += line;
441 for (size_t field = 0; field < LatencyAccounting::Count; ++field) {
442 line.Format(" %20lu", snapshot.values[field]);
443 contents += line;
444 }
445 contents += String("\n");
446 }
447 return true;
448 }
449};
450
451class LoadAverageFile final : public GeneratedFile {
452 public:
453 LoadAverageFile(uintptr_t inode, ProcFs& filesystem, File* parent)
454 : GeneratedFile(String("loadavg"), inode, filesystem, parent) {}
455
456 private:
457 bool generate(String& contents) const override {
458 const auto activity = Scheduler::instance().systemActivity();
459 const TaskCounts tasks = taskCounts();
460 uint64_t whole[3], fraction[3];
461 for (size_t i = 0; i < 3; ++i) {
462 const uint64_t hundredths =
463 (activity.loads[i] * 100 + LoadAverage::Scale / 2) / LoadAverage::Scale;
464 whole[i] = hundredths / 100;
465 fraction[i] = hundredths % 100;
466 }
467 contents.Format("%lu.%02lu %lu.%02lu %lu.%02lu %lu/%lu %lu\n", whole[0], fraction[0], whole[1],
468 fraction[1], whole[2], fraction[2], tasks.runnable, tasks.total, tasks.lastPid);
469 return true;
470 }
471};
472
473class SystemStatFile final : public GeneratedFile {
474 public:
475 SystemStatFile(uintptr_t inode, ProcFs& filesystem, File* parent)
476 : GeneratedFile(String("stat"), inode, filesystem, parent) {}
477
478 private:
479 bool generate(String& contents) const override {
480 const auto activity = Scheduler::instance().systemActivity();
481 const TaskCounts tasks = taskCounts();
482 const uint64_t user = activity.userNanoseconds / NanosecondsPerClockTick;
483 const uint64_t kernel = activity.kernelNanoseconds / NanosecondsPerClockTick;
484 const uint64_t idle = activity.idleNanoseconds / NanosecondsPerClockTick;
485 const uint64_t uptime = Time::getTicks() / Time::Multiplier::Second;
486 const uint64_t realtime = Time::getTime();
487 const uint64_t bootTime = realtime > uptime ? realtime - uptime : 0;
488 contents.Format(
489 "cpu %lu 0 %lu %lu 0 0 0 0 0 0\n"
490 "btime %lu\n"
491 "procs_running %lu\n"
492 "procs_blocked 0\n",
493 user, kernel, idle, bootTime, tasks.runnable);
494 return true;
495 }
496};
497
498class CpuInfoFile final : public GeneratedFile {
499 public:
500 CpuInfoFile(uintptr_t inode, ProcFs& filesystem, File* parent)
501 : GeneratedFile(String("cpuinfo"), inode, filesystem, parent) {}
502
503 private:
504 bool generate(String& contents) const override {
505 for (size_t processor = 0; processor < Processor::getCount(); ++processor) {
506 String entry;
507 entry.Format(
508 "processor\t: %lu\n"
509 "vendor_id\t: Pedigree\n"
510 "model name\t: Pedigree virtual processor\n"
511 "flags\t\t:\n\n",
512 processor);
513 contents += entry;
514 }
515 return true;
516 }
517};
518
519class PartitionsFile final : public GeneratedFile {
520 public:
521 PartitionsFile(uintptr_t inode, ProcFs& filesystem, File* parent)
522 : GeneratedFile(String("partitions"), inode, filesystem, parent) {}
523
524 private:
525 bool generate(String& contents) const override {
526 contents = String("major minor #blocks name\n\n");
527 uint32_t ids[DiskEndpoints::Capacity];
528 const size_t count = DiskEndpoints::snapshot(ids, DiskEndpoints::Capacity);
529 for (size_t index = 0; index < count; ++index) {
530 uint64_t bytes = 0;
531 if (!DiskEndpoints::describe(ids[index], bytes))
532 continue;
533 String line;
534 line.Format("%5u %5u %9lu disk%u\n", PosixBlock::PhysicalMajor, ids[index], bytes / 1024,
535 ids[index]);
536 contents += line;
537 }
538 return true;
539 }
540};
541
542class ProcessFile : public GeneratedFile {
543 public:
544 ProcessFile(const String& name, uintptr_t inode, ProcFs& filesystem, File* parent, size_t pid)
545 : GeneratedFile(name, inode, filesystem, parent), m_Pid(pid) {}
546
547 protected:
548 bool acquire(Scheduler::ProcessLease& process) const {
549 return Scheduler::instance().acquireProcessByUserspaceId(
550 process, m_Pid, static_cast<ProcFs*>(getFilesystem())->pidNamespace().get()) &&
551 process->getType() == Process::Posix;
552 }
553
554 const UserspacePidNamespace* pidNamespace() const {
555 return static_cast<ProcFs*>(getFilesystem())->pidNamespace().get();
556 }
557
558 private:
559 size_t m_Pid;
560};
561
562String processName(Process& process) {
563 const LargeStaticString& description = process.description();
564 size_t start = 0;
565 for (size_t i = 0; i < description.length(); ++i)
566 if (description[i] == '/')
567 start = i + 1;
568 size_t length = description.length() - start;
569 if (length > 15)
570 length = 15;
571 return String(static_cast<const char*>(description) + start, length);
572}
573
574size_t parentId(Process& process, const UserspacePidNamespace* space) {
575 while (Process* expected = process.getParent()) {
577 if (!Scheduler::instance().acquireProcess(parent, expected)) {
578 if (process.getParent() != expected)
579 continue;
580 return 0;
581 }
582 if (process.getParent() == parent.get())
583 return parent->getUserspaceId(space);
584 }
585 return 0;
586}
587
588char processState(Process& process) {
589 const auto state = process.getState();
590 if (state == Process::Suspended)
591 return 'T';
592 if (state == Process::Terminated || state == Process::Reaped)
593 return 'Z';
594 bool sleeping = false;
595 const size_t count = process.getNumThreads();
596 for (size_t i = 0; i < count; ++i) {
598 if (!process.acquireThread(thread, i))
599 continue;
600 const auto status = thread->getStatus();
601 if (status == Thread::Ready || status == Thread::Running)
602 return 'R';
603 if (status == Thread::Sleeping || status == Thread::AwaitingJoin)
604 sleeping = true;
605 }
606 return sleeping ? 'S' : 'R';
607}
608
609String mountinfoEscape(const String& value) {
610 String result;
611 for (size_t i = 0; i < value.length(); ++i) {
612 switch (value[i]) {
613 case ' ':
614 result += "\\040";
615 break;
616 case '\t':
617 result += "\\011";
618 break;
619 case '\n':
620 result += "\\012";
621 break;
622 case '\\':
623 result += "\\134";
624 break;
625 default:
626 result += String(value.cstr() + i, 1);
627 break;
628 }
629 }
630 return result;
631}
632
633class ProcessMountInfoFile final : public ProcessFile {
634 public:
635 ProcessMountInfoFile(uintptr_t inode, ProcFs& filesystem, File* parent, size_t pid)
636 : ProcessFile(String("mountinfo"), inode, filesystem, parent, pid) {}
637
638 private:
639 bool generate(String& contents) const override {
641 if (!acquire(process)) {
642 return false;
643 }
644 auto context = process->acquireFilesystemContext();
645 auto* view = VfsMountView::fromContext(context);
647 if (!view || !view->snapshotMounts(context, mounts)) {
648 return false;
649 }
650 for (const auto& mount : mounts) {
651 VFS::MountOperation backing;
652 if (!mount.backing.acquire(backing)) {
653 continue;
654 }
655 struct stat attributes = {};
656 if (!posix_stat_file("", backing.filesystem()->getRoot(), &attributes)) {
657 return false;
658 }
659 const unsigned major = PosixMetadata::deviceMajor(attributes.st_dev);
660 const unsigned minor = PosixMetadata::deviceMinor(attributes.st_dev);
661 String options(mount.flags & VfsMountView::ReadOnly ? "ro" : "rw");
662 if (mount.flags & VfsMountView::NoSuid) {
663 options += ",nosuid";
664 }
665 if (mount.flags & VfsMountView::NoDev) {
666 options += ",nodev";
667 }
668 if (mount.flags & VfsMountView::NoExec) {
669 options += ",noexec";
670 }
671 const String root = mountinfoEscape(mount.root);
672 const String path = mountinfoEscape(mount.path);
673 const String kind = mountinfoEscape(backing.filesystem()->getVolumeLabel());
674 String line;
675 line.Format("%lu %lu %u:%u %s %s %s - %s none %s\n", mount.id, mount.parentId, major, minor,
676 root.cstr(), path.cstr(), options.cstr(), kind.cstr(),
677 mount.flags & VfsMountView::ReadOnly ? "ro" : "rw");
678 contents += line;
679 }
680 return true;
681 }
682};
683
684class ProcessStatFile final : public ProcessFile {
685 public:
686 ProcessStatFile(uintptr_t inode, ProcFs& filesystem, File* parent, size_t pid)
687 : ProcessFile(String("stat"), inode, filesystem, parent, pid) {}
688
689 private:
690 bool generate(String& contents) const override {
692 if (!acquire(lease))
693 return false;
694 auto& process = *static_cast<PosixProcess*>(lease.get());
695 size_t processGroup = 0;
696 process.getProcessGroupId(processGroup, pidNamespace());
697 const uint64_t user = process.getUserTime() / NanosecondsPerClockTick;
698 const uint64_t kernel = process.getKernelTime() / NanosecondsPerClockTick;
699 const uint64_t childrenUser = process.getReapedChildrenUserTime() / NanosecondsPerClockTick;
700 const uint64_t childrenKernel = process.getReapedChildrenKernelTime() / NanosecondsPerClockTick;
701 const uint64_t start = process.getStartTimeTicks() / NanosecondsPerClockTick;
702 const ssize_t virtualPages = process.getVirtualPageCount();
703 const ssize_t residentPages = process.getPhysicalPageCount();
704 const uint64_t virtualBytes = virtualPages > 0 ? static_cast<uint64_t>(virtualPages) *
706 : 0;
707 const long resident = residentPages > 0 ? residentPages : 0;
708 // Proc consumers advance by field number even when they ignore the value.
709 contents.Format(
710 "%lu (%s) %c %lu %lu %lu 0 0 0 0 0 0 0 %lu %lu %lu %lu 20 0 %lu 0 %lu %lu %ld"
711 " 0 0 0 0 0 0 0"
712 " 0 0 0 0 0 0 0"
713 " 0 0 0 0 0 0 0"
714 " 0 0 0 0 0 0 0\n",
715 process.getUserspaceId(pidNamespace()), processName(process).cstr(), processState(process),
716 parentId(process, pidNamespace()), processGroup, process.getSessionId(pidNamespace()), user,
717 kernel, childrenUser, childrenKernel, process.getNumThreads(), start, virtualBytes,
718 resident);
719 return true;
720 }
721};
722
723class ProcessStatmFile final : public ProcessFile {
724 public:
725 ProcessStatmFile(uintptr_t inode, ProcFs& filesystem, File* parent, size_t pid)
726 : ProcessFile(String("statm"), inode, filesystem, parent, pid) {}
727
728 private:
729 bool generate(String& contents) const override {
731 if (!acquire(process))
732 return false;
733 const ssize_t total = process->getVirtualPageCount();
734 const ssize_t resident = process->getPhysicalPageCount();
735 const ssize_t shared = process->getSharedPageCount();
736 contents.Format("%ld %ld %ld 0 0 0 0\n", total > 0 ? total : 0, resident > 0 ? resident : 0,
737 shared > 0 ? shared : 0);
738 return true;
739 }
740};
741
742class ProcessStatusFile final : public ProcessFile {
743 public:
744 ProcessStatusFile(uintptr_t inode, ProcFs& filesystem, File* parent, size_t pid)
745 : ProcessFile(String("status"), inode, filesystem, parent, pid) {}
746
747 private:
748 bool generate(String& contents) const override {
750 if (!acquire(lease))
751 return false;
752 auto& process = *static_cast<PosixProcess*>(lease.get());
753 const auto credentials = process.snapshotCredentials();
754 const ssize_t virtualPages = process.getVirtualPageCount();
755 const ssize_t residentPages = process.getPhysicalPageCount();
756 const uint64_t kilobytesPerPage = PhysicalMemoryManager::getPageSize() / 1024;
757 contents.Format(
758 "Name:\t%s\n"
759 "State:\t%c\n"
760 "Tgid:\t%lu\n"
761 "Pid:\t%lu\n"
762 "PPid:\t%lu\n"
763 "Uid:\t%lu\t%lu\t%lu\t%lu\n"
764 "Gid:\t%lu\t%lu\t%lu\t%lu\n"
765 "Threads:\t%lu\n"
766 "VmSize:\t%lu kB\n"
767 "VmRSS:\t%lu kB\n",
768 processName(process).cstr(), processState(process), process.getUserspaceId(pidNamespace()),
769 process.getUserspaceId(pidNamespace()), parentId(process, pidNamespace()), credentials.ruid,
770 credentials.euid, credentials.suid, credentials.euid, credentials.rgid, credentials.egid,
771 credentials.sgid, credentials.egid, process.getNumThreads(),
772 virtualPages > 0 ? static_cast<uint64_t>(virtualPages) * kilobytesPerPage : 0,
773 residentPages > 0 ? static_cast<uint64_t>(residentPages) * kilobytesPerPage : 0);
774 return true;
775 }
776};
777
778class ProcessCommandLineFile final : public ProcessFile {
779 public:
780 ProcessCommandLineFile(uintptr_t inode, ProcFs& filesystem, File* parent, size_t pid)
781 : ProcessFile(String("cmdline"), inode, filesystem, parent, pid) {}
782
783 uint64_t readBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool) override {
784 Vector<String> arguments;
785 if (!snapshot(arguments))
786 return 0;
787 uint64_t copied = 0;
788 for (const auto& argument : arguments) {
789 const uint64_t fieldSize = argument.length() + 1;
790 if (location >= fieldSize) {
791 location -= fieldSize;
792 continue;
793 }
794 const uint64_t available = fieldSize - location;
795 const uint64_t amount = min(size - copied, available);
796 const char* field = argument.length() ? argument.cstr() : "";
797 MemoryCopy(reinterpret_cast<void*>(buffer + copied), field + location, amount);
798 copied += amount;
799 location = 0;
800 if (copied == size)
801 break;
802 }
803 return copied;
804 }
805
806 size_t getSize() override {
807 Vector<String> arguments;
808 if (!snapshot(arguments))
809 return 0;
810 size_t size = 0;
811 for (const auto& argument : arguments)
812 size += argument.length() + 1;
813 return size;
814 }
815
816 private:
817 bool snapshot(Vector<String>& arguments) const {
819 if (!acquire(lease))
820 return false;
821 auto* subsystem = static_cast<PosixSubsystem*>(lease->getSubsystem());
822 return subsystem && subsystem->commandLine(arguments);
823 }
824
825 bool generate(String& contents) const override {
826 contents.clear();
827 return false;
828 }
829};
830} // namespace
831
832bool procfsAddSystemStatusFiles(ProcFs& filesystem, ProcFsDirectory& root) {
833 auto* loadAverage = new LoadAverageFile(filesystem.getNextInode(), filesystem, &root);
834 auto* stat = new SystemStatFile(filesystem.getNextInode(), filesystem, &root);
835 auto* cpuInfo = new CpuInfoFile(filesystem.getNextInode(), filesystem, &root);
836 auto* partitions = new PartitionsFile(filesystem.getNextInode(), filesystem, &root);
837 auto* metrics = new MetricsFile(filesystem.getNextInode(), filesystem, &root);
838 auto* kernel = new ProcFsDirectory(String("kernel"), 0, 0, 0, filesystem.getNextInode(),
839 &filesystem, 0, &root);
840 auto* threads =
841 kernel ? new KernelThreadsFile(filesystem.getNextInode(), filesystem, kernel) : nullptr;
842 auto* latency = kernel ? new LatencyFile(filesystem.getNextInode(), filesystem, kernel) : nullptr;
843 if (!loadAverage || !stat || !cpuInfo || !partitions || !metrics || !kernel || !threads ||
844 !latency) {
845 delete loadAverage;
846 delete stat;
847 delete cpuInfo;
848 delete partitions;
849 delete metrics;
850 delete threads;
851 delete latency;
852 delete kernel;
853 return false;
854 }
855 root.addEntry(loadAverage->getName(), loadAverage);
856 root.addEntry(stat->getName(), stat);
857 root.addEntry(cpuInfo->getName(), cpuInfo);
858 root.addEntry(partitions->getName(), partitions);
859 root.addEntry(metrics->getName(), metrics);
860 kernel->setPermissions(FILE_UR | FILE_UX | FILE_GR | FILE_GX | FILE_OR | FILE_OX);
861 kernel->addEntry(threads->getName(), threads);
862 kernel->addEntry(latency->getName(), latency);
863 root.addEntry(kernel->getName(), kernel);
864 return true;
865}
866
867bool procfsAddProcessStatusFiles(ProcFs& filesystem, ProcFsDirectory& directory, size_t pid) {
868 auto* stat = new ProcessStatFile(filesystem.getNextInode(), filesystem, &directory, pid);
869 auto* statm = new ProcessStatmFile(filesystem.getNextInode(), filesystem, &directory, pid);
870 auto* status = new ProcessStatusFile(filesystem.getNextInode(), filesystem, &directory, pid);
871 auto* commandLine =
872 new ProcessCommandLineFile(filesystem.getNextInode(), filesystem, &directory, pid);
873 auto* mountinfo =
874 new ProcessMountInfoFile(filesystem.getNextInode(), filesystem, &directory, pid);
875 if (!stat || !statm || !status || !commandLine || !mountinfo) {
876 delete stat;
877 delete statm;
878 delete status;
879 delete commandLine;
880 delete mountinfo;
881 return false;
882 }
883 directory.addEntry(stat->getName(), stat);
884 directory.addEntry(statm->getName(), statm);
885 directory.addEntry(status->getName(), status);
886 directory.addEntry(commandLine->getName(), commandLine);
887 directory.addEntry(mountinfo->getName(), mountinfo);
888 return true;
889}
Definition File.h:75
virtual uint64_t readBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true)
Definition File.cc:1369
virtual bool isBytewise() const
Definition File.cc:1365
void setGidOnly(size_t gid)
Definition File.cc:1448
void setPermissionsOnly(uint32_t perms)
Definition File.cc:1440
void setUidOnly(size_t uid)
Definition File.cc:1444
virtual uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true)
Definition File.cc:1376
virtual File * openForDescriptor(RetainedFile &owner)
Definition File.cc:1361
virtual const String & getVolumeLabel() const =0
virtual File * getRoot() const =0
static PhysicalMemoryManager & instance()
size_t getUserspaceId() const
Definition Process.h:504
@ Reaped
Terminal wait status is visible; the owner may still be on-stack.
Definition Process.h:308
size_t getId()
Definition Process.h:499
Process * getParent()
Definition Process.h:620
size_t getNumThreads()
Definition Process.cc:1380
LargeStaticString & description()
Definition Process.h:525
MUST_USE_RESULT bool acquireThread(ThreadLease &lease, size_t n)
Definition Process.cc:1385
Time::Timestamp getUserTime() const
Definition Process.h:829
static size_t getCount()
This class manages how processes and threads are scheduled across processors.
Definition Scheduler.h:50
static Scheduler & instance()
Definition Scheduler.h:96
size_t getNumProcesses()
Definition Scheduler.cc:268
MUST_USE_RESULT bool acquireProcess(ProcessLease &lease, size_t n)
Definition Scheduler.cc:275
Status getStatus() const
Definition Thread.h:433
class PerProcessorScheduler * getScheduler() const
Definition Thread.h:929
static VFS & instance()
Definition VFS.cc:311
A vector / dynamic array.
Definition Vector.h:33