SchOS/include/fsstat.h
Nexoniarz 26bca020b6 licence: SchOS is GPL-2.0-or-later, no longer Apache-2.0
Nexoniarz is the sole author and SchOS was never published under
Apache-2.0, so this is a straight relicence. LICENSE is the FSF GPLv2 text,
every first-party SPDX header moves to GPL-2.0-or-later, and NOTICE,
README.md, docs/licensing.md and the site say so. projects/schfs-linux
follows the rest of the tree.

The GPL gives copyleft: a fork has to ship its source. It also lifts the
old "Linux drivers, never" rule, since GPL-2.0-only code combines with
GPL-2.0-or-later. "Or later" leaves room for GPLv3, but one binary still
has to pick one version, so docs/licensing.md now keeps Apache-2.0 and
GPLv3 code out of the kernel to keep Linux code possible there. BSD
4-clause code goes from "avoid" to "no", because the advertising clause
is GPL-incompatible.

LDLS still ships no .ko. That boundary is now project policy rather than
a legal requirement, and ldls.md, devices.md and licensing.md section 6
say so. refmod declares license=GPL, and ldls-check passes (27 checks).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:48:45 +02:00

71 lines
2.8 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
//
// Counters for the filesystem and block paths, read through /proc/fsstat.
//
// These exist because "the system feels slow" is not a bug report anyone can
// act on, and every guess about where the time goes in a filesystem is wrong
// until it is measured. `ls /bin` taking three seconds could be the directory
// walk, the tree descents, the copy path, or the disk -- and those four call
// for completely different fixes.
//
// Deliberately plain `uint64` with no locking. A counter that is off by one
// because two CPUs raced tells you the same thing as one that is exact, and a
// lock here would change the very timings it is meant to report.
#pragma once
struct fsstat {
// Buffer cache. `bread_miss` is the one that matters: a miss is a real
// transfer, a hit is a memcpy.
uint64 bread_calls;
uint64 bread_miss;
uint64 bget_evict_dirty; // had to write a buffer out to make room
// Actual device transfers, and the cycles spent inside them.
uint64 disk_reads;
uint64 disk_writes;
uint64 disk_cycles;
// The VFS read path: one call is one read(2) reaching a SchFS file.
uint64 read_calls;
uint64 read_bytes;
uint64 read_cycles;
// Directory listing.
uint64 getdents_calls;
uint64 getdents_cycles;
uint64 getdents_walked; // entries stepped over, including ones re-walked
// B-tree descents from the root, and the block reads they cost.
uint64 descents;
// exec(2), which on this system means reading an 800 KB multicall binary.
uint64 exec_calls;
uint64 exec_cycles;
// ---------------------------------------------------------------------
// Table occupancy, for finding leaks.
//
// `_used` is the live count; `_peak` is the high-water mark, and the peak is
// the number that answers the question. A live count only tells you about the
// moment you looked, and a reference leak is invisible in it right up until
// the table is full -- at which point iget() panics and filealloc() returns
// 0, which is where the trail goes cold.
//
// Maintained at the 0->1 and 1->0 transitions rather than by scanning, so
// they cost one increment on a path that already holds the relevant lock.
//
// What to watch: run a long ls-heavy session and read /proc/fsstat between
// commands. A peak that climbs and never comes back down after the commands
// finish is the leak, and which of the three it is says where to look.
uint64 inode_used, inode_peak;
uint64 file_used, file_peak;
uint64 buf_pinned, buf_pinned_peak;
};
// Bump a (used, peak) pair. Callers hold the table's own lock already.
#define FSSTAT_TAKE(used, peak) \
do { if (++(used) > (peak)) (peak) = (used); } while (0)
#define FSSTAT_DROP(used) \
do { if ((used) > 0) (used)--; } while (0)
extern struct fsstat fsstat;