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>
71 lines
2.8 KiB
C
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;
|