- hypr/apps.lua - hypr/autostart.lua - hypr/envs.lua - hypr/hyprland.lua - hypr/hyprsunset.conf - hypr/input.lua - hypr/looknfeel.lua - hypr/omasettings.lua - hypr/xdph.conf - omarchy/branding/about.txt - omarchy/branding/screensaver.txt - omarchy/extensions/omarchy-menu.jsonc - omarchy/hooks/battery-low.d/play-warning-sound.sample - omarchy/hooks/font-set.d/show-font-notification.sample - omarchy/hooks/post-boot.d/weather.sample - omarchy/hooks/post-update.d/install-voxtype.hook - omarchy/hooks/post-update.d/setup-agent.hook - omarchy/hooks/post-update.d/setup-fingerprint.hook - omarchy/hooks/post-update.d/show-update-notification.sample - omarchy/hooks/pre-refresh-pacman.d/add-custom-repo.sample - omarchy/hooks/theme-set.d/show-theme-notification.sample - omarchy/shell.json - omarchy/shell.toml - omarchy/theme.name - omarchy/themes/azure-glow/README.md - omarchy/themes/azure-glow/alacritty.toml - omarchy/themes/azure-glow/btop.theme - omarchy/themes/azure-glow/hyprland.conf - omarchy/themes/azure-glow/hyprlock.conf - omarchy/themes/azure-glow/icons.theme - … 269 more
707 lines
30 KiB
Bash
707 lines
30 KiB
Bash
#!/bin/bash
|
|
|
|
set -euo pipefail
|
|
|
|
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
|
|
|
run_node_test <<'JS'
|
|
const activity = requireFromRoot('Model.js')
|
|
|
|
const storage = activity.parseStorageSnapshot([
|
|
'schema\tactivity-storage\t1',
|
|
'sample\t99.75',
|
|
'storage\t/\t4096000\t3072000\t819200',
|
|
''
|
|
].join('\n'))
|
|
|
|
assertEqual(storage.schema, 1, 'activity parses the storage schema version')
|
|
assertEqual(storage.sample, 99.75, 'activity parses the storage monotonic sample')
|
|
assertEqual(storage.path, '/', 'activity identifies the root filesystem')
|
|
assertEqual(storage.total, 4096000, 'activity parses total storage bytes')
|
|
assertEqual(storage.used, 3072000, 'activity parses used storage bytes')
|
|
assertEqual(storage.available, 819200, 'activity parses user-available storage bytes')
|
|
assertEqual(storage.volumes.length, 1, 'activity keeps a single storage line as one volume')
|
|
|
|
const volumes = activity.parseStorageSnapshot([
|
|
'schema\tactivity-storage\t1',
|
|
'sample\t12',
|
|
'storage\t/data\t200\t180\t20',
|
|
'storage\t/\t100\t20\t80',
|
|
''
|
|
].join('\n'))
|
|
assertEqual(volumes.volumes.length, 2, 'activity keeps multiple storage volumes')
|
|
assertEqual(volumes.path, '/', 'activity still exposes root as the primary storage path')
|
|
assertEqual(volumes.volumes[0].path, '/', 'activity sorts the root volume first')
|
|
assertEqual(volumes.volumes[1].path, '/data', 'activity keeps additional mountpoints')
|
|
assertEqual(activity.storageVolumeLabel('/'), '/', 'activity labels the root volume as /')
|
|
assertEqual(activity.storageVolumeLabel('/mnt/data'), 'data', 'activity labels a volume by its last path component')
|
|
assertEqual(activity.tightestStorageVolume(volumes.volumes).path, '/data', 'activity prefers the fullest volume')
|
|
assertEqual(activity.selectStorageVolume(volumes.volumes, '', false).path, '/data', 'activity defaults storage to the fullest volume')
|
|
assertEqual(activity.selectStorageVolume(volumes.volumes, '/', true).path, '/', 'activity keeps a pinned storage volume')
|
|
assertEqual(activity.selectStorageVolume(volumes.volumes, '/gone', true).path, '/data', 'activity falls back when a pinned volume disappears')
|
|
assertEqual(activity.cycleItemId(volumes.volumes, '/data', 'path', 1), '/', 'activity wraps the storage pager')
|
|
assertEqual(activity.nextCycleIndex(3, 0, -1), 2, 'activity wraps a pager backward')
|
|
|
|
const invalidStorage = activity.parseStorageSnapshot(
|
|
'schema\tactivity-storage\t1\nstorage\t/\t100\t120\t110\n'
|
|
)
|
|
assertEqual(invalidStorage.used, 100, 'activity bounds used storage by filesystem size')
|
|
assertEqual(invalidStorage.available, 100, 'activity bounds available storage by filesystem size')
|
|
|
|
const firstGpuSnapshot = activity.parseGpuSnapshot([
|
|
'schema\tactivity-gpus\t1',
|
|
'sample\t10',
|
|
'gpu\t0000:00:02.0\tIntel\txe\tIntel Arc Test\t-1\t-1\t-1\tunknown\t600',
|
|
'engine\t0000:00:02.0\tclient-a\trcs\t100\t1000\t1\tcycles',
|
|
'engine\t0000:00:02.0\tclient-b\trcs\t50\t1000\t1\tcycles',
|
|
'engine\t0000:00:02.0\tclient-a\tvcs\t40\t1000\t1\tcycles',
|
|
'gpu-memory\t0000:00:02.0\tshared\t536870912',
|
|
'gpu\t0000:04:00.0\tNVIDIA\tnvidia\tNVIDIA GeForce Test\t62\t2147483648\t8589934592\tvram\t1950',
|
|
''
|
|
].join('\n'))
|
|
const secondGpuSnapshot = activity.parseGpuSnapshot([
|
|
'schema\tactivity-gpus\t1',
|
|
'sample\t12',
|
|
'gpu\t0000:00:02.0\tIntel\txe\tIntel Arc Test\t-1\t-1\t-1\tunknown\t700',
|
|
'engine\t0000:00:02.0\tclient-a\trcs\t200\t1200\t1\tcycles',
|
|
'engine\t0000:00:02.0\tclient-b\trcs\t100\t1200\t1\tcycles',
|
|
'engine\t0000:00:02.0\tclient-a\tvcs\t80\t1200\t1\tcycles',
|
|
'gpu-memory\t0000:00:02.0\tshared\t805306368',
|
|
'gpu\t0000:04:00.0\tNVIDIA\tnvidia\tNVIDIA GeForce Test\t47\t3221225472\t8589934592\tvram\t1905',
|
|
''
|
|
].join('\n'))
|
|
const sampledGpus = activity.nextGpus(firstGpuSnapshot, secondGpuSnapshot)
|
|
|
|
assertEqual(firstGpuSnapshot.schema, 1, 'activity parses the GPU schema version')
|
|
assertEqual(firstGpuSnapshot.gpus.length, 2, 'activity keeps multiple GPU adapters separate')
|
|
assertEqual(firstGpuSnapshot.gpus[0].memoryKind, 'shared', 'activity labels DRM allocation as shared GPU memory')
|
|
assertEqual(firstGpuSnapshot.gpus[0].memoryUsed, 536870912, 'activity parses resident shared GPU memory')
|
|
assertEqual(sampledGpus[0].usage, 75, 'activity sums clients within an engine and avoids adding parallel engine classes')
|
|
assertEqual(sampledGpus[0].frequencyMHz, 700, 'activity carries the current Intel GPU clock into display metrics')
|
|
assertEqual(sampledGpus[0].memoryUsed, 805306368, 'activity carries current shared allocation into display metrics')
|
|
assertEqual(sampledGpus[1].usage, 47, 'activity prefers a native GPU utilization reading')
|
|
assertEqual(sampledGpus[1].frequencyMHz, 1905, 'activity parses the NVIDIA graphics clock')
|
|
assertEqual(sampledGpus[1].memoryTotal, 8589934592, 'activity exposes dedicated VRAM capacity')
|
|
|
|
const firstTimedGpu = activity.parseGpuSnapshot(
|
|
'schema\tactivity-gpus\t1\nsample\t20\ngpu\t0000:03:00.0\tAMD\tamdgpu\tAMD Test\t-1\t-1\t-1\tunknown\t1800\n'
|
|
+ 'engine\t0000:03:00.0\t7\tgfx\t1000000000\t-1\t1\ttime\n'
|
|
)
|
|
const secondTimedGpu = activity.parseGpuSnapshot(
|
|
'schema\tactivity-gpus\t1\nsample\t22\ngpu\t0000:03:00.0\tAMD\tamdgpu\tAMD Test\t-1\t-1\t-1\tunknown\t1750\n'
|
|
+ 'engine\t0000:03:00.0\t7\tgfx\t2000000000\t-1\t1\ttime\n'
|
|
)
|
|
assertEqual(
|
|
activity.nextGpus(firstTimedGpu, secondTimedGpu)[0].usage,
|
|
50,
|
|
'activity derives utilization from standard DRM nanosecond engine counters'
|
|
)
|
|
assertEqual(
|
|
activity.nextGpus(activity.emptyGpuSnapshot(), firstGpuSnapshot)[0].usage,
|
|
-1,
|
|
'activity waits for a baseline before presenting counter-based GPU usage'
|
|
)
|
|
|
|
const thermals = activity.parseThermalSnapshot([
|
|
'schema\tactivity-thermals\t1',
|
|
'sample\t100.25',
|
|
'temperature\thwmon0/temp1\tcoretemp\tPackage id 0\t62500',
|
|
''
|
|
].join('\n'))
|
|
|
|
assertEqual(thermals.schema, 1, 'activity parses the thermal schema version')
|
|
assertEqual(thermals.sample, 100.25, 'activity parses the thermal monotonic sample')
|
|
assertEqual(thermals.temperatures[0].value, 62.5, 'activity parses the CPU temperature')
|
|
|
|
const resources = activity.parseSnapshot(
|
|
'schema\tactivity-resources\t1\nsample\t100.00\nmemory\tMemTotal\t1024\n'
|
|
)
|
|
assertEqual(
|
|
activity.cpuTemperature(thermals.temperatures).label,
|
|
'Package id 0',
|
|
'activity selects CPU temperature from its independent thermal snapshot'
|
|
)
|
|
assertEqual(
|
|
Object.prototype.hasOwnProperty.call(resources, 'temperatures'),
|
|
false,
|
|
'activity keeps thermal state out of resource snapshots'
|
|
)
|
|
assertDeepEqual(
|
|
activity.emptyPower(),
|
|
{ available: false, watts: -1 },
|
|
'activity exposes a dedicated empty power model'
|
|
)
|
|
|
|
const firstPower = activity.parsePackagePowerSnapshot([
|
|
'schema\tactivity-process-power\t1',
|
|
'sample\t200',
|
|
'package\tintel-rapl:0\tpackage-0\t2000000\t10000000',
|
|
'package\tintel-rapl:1\tpackage-1\t4000000\t20000000',
|
|
''
|
|
].join('\n'))
|
|
const secondPower = activity.parsePackagePowerSnapshot([
|
|
'schema\tactivity-process-power\t1',
|
|
'sample\t202',
|
|
'package\tintel-rapl:0\tpackage-0\t6000000\t10000000',
|
|
'package\tintel-rapl:1\tpackage-1\t10000000\t20000000',
|
|
''
|
|
].join('\n'))
|
|
const power = activity.nextPackagePower(firstPower, secondPower)
|
|
assertEqual(power.watts, 5, 'activity sums package power from raw energy deltas')
|
|
assertEqual(power.available, true, 'activity exposes complete multi-package measurements')
|
|
assertEqual(
|
|
activity.packageEnergyReadable(secondPower),
|
|
true,
|
|
'activity accepts a complete set of readable package counters'
|
|
)
|
|
|
|
const metadataOnlyPower = activity.parsePackagePowerSnapshot([
|
|
'schema\tactivity-process-power\t1',
|
|
'sample\t203',
|
|
'package\tintel-rapl:0\tpackage-0\t\t10000000',
|
|
''
|
|
].join('\n'))
|
|
const metadataOnlyDerived = activity.nextPackagePower(activity.emptyPackagePowerSnapshot(), metadataOnlyPower)
|
|
assertEqual(metadataOnlyPower.packages[0].energy, -1, 'activity marks an unreadable energy counter')
|
|
assertEqual(metadataOnlyDerived.available, false, 'activity does not invent watts without an energy counter')
|
|
assertEqual(
|
|
activity.packageEnergyReadable(metadataOnlyPower),
|
|
false,
|
|
'activity rejects package metadata without a readable energy counter'
|
|
)
|
|
|
|
const beforeWrap = activity.parsePackagePowerSnapshot(
|
|
'schema\tactivity-process-power\t1\nsample\t300\npackage\tintel-rapl:0\tpackage-0\t9000000\t10000000\n'
|
|
)
|
|
const afterWrap = activity.parsePackagePowerSnapshot(
|
|
'schema\tactivity-process-power\t1\nsample\t302\npackage\tintel-rapl:0\tpackage-0\t1000000\t10000000\n'
|
|
)
|
|
assertEqual(
|
|
activity.nextPackagePower(beforeWrap, afterWrap).watts,
|
|
1,
|
|
'activity handles a RAPL energy counter wrap'
|
|
)
|
|
|
|
const beforeReset = activity.parsePackagePowerSnapshot(
|
|
'schema\tactivity-process-power\t1\nsample\t600\npackage\tintel-rapl:0\tpackage-0\t6000000\t10000000\n'
|
|
)
|
|
const afterReset = activity.parsePackagePowerSnapshot(
|
|
'schema\tactivity-process-power\t1\nsample\t602\npackage\tintel-rapl:0\tpackage-0\t1000000\t10000000\n'
|
|
)
|
|
assertEqual(
|
|
activity.nextPackagePower(beforeReset, afterReset).watts,
|
|
-1,
|
|
'activity rejects a decreasing RAPL counter away from its wrap boundary'
|
|
)
|
|
assertEqual(
|
|
activity.packageEnergyDelta(beforeReset.packages[0], afterReset.packages[0]),
|
|
0,
|
|
'activity treats a decreasing mid-range RAPL counter as reset'
|
|
)
|
|
|
|
const beforeMultiSocketPower = activity.parsePackagePowerSnapshot([
|
|
'schema\tactivity-process-power\t1',
|
|
'sample\t200',
|
|
'package\tintel-rapl:0\tpackage-0\t2000000\t10000000',
|
|
'package\tintel-rapl:1\tpackage-1\t4000000\t20000000',
|
|
''
|
|
].join('\n'))
|
|
const partialSocketPower = activity.parsePackagePowerSnapshot([
|
|
'schema\tactivity-process-power\t1',
|
|
'sample\t202',
|
|
'package\tintel-rapl:0\tpackage-0\t6000000\t10000000',
|
|
'package\tintel-rapl:1\tpackage-1\t\t20000000',
|
|
''
|
|
].join('\n'))
|
|
assertEqual(
|
|
activity.nextPackagePower(beforeMultiSocketPower, partialSocketPower).available,
|
|
false,
|
|
'activity does not present partial multi-socket energy as whole-system power'
|
|
)
|
|
assertEqual(
|
|
activity.packageEnergyReadable(partialSocketPower),
|
|
false,
|
|
'activity retries privilege when any processor package is unreadable'
|
|
)
|
|
|
|
const changedRange = activity.parsePackagePowerSnapshot(
|
|
'schema\tactivity-process-power\t1\nsample\t304\npackage\tintel-rapl:0\tpackage-0\t2000000\t20000000\n'
|
|
)
|
|
assertEqual(
|
|
activity.nextPackagePower(afterWrap, changedRange).watts,
|
|
-1,
|
|
'activity treats a changed RAPL range as a new counter'
|
|
)
|
|
assertEqual(
|
|
activity.packageEnergyDelta(afterWrap.packages[0], changedRange.packages[0]),
|
|
0,
|
|
'activity rejects a changed RAPL counter range'
|
|
)
|
|
const beforePackageWrap = activity.parsePackagePowerSnapshot(
|
|
'schema\tactivity-process-power\t1\nsample\t500\npackage\tintel-rapl:0\tpackage-0\t9000000\t10000000\n'
|
|
)
|
|
const afterPackageWrap = activity.parsePackagePowerSnapshot(
|
|
'schema\tactivity-process-power\t1\nsample\t502\npackage\tintel-rapl:0\tpackage-0\t1000000\t10000000\n'
|
|
)
|
|
assertEqual(
|
|
activity.nextPackagePower(beforePackageWrap, afterPackageWrap).watts,
|
|
1,
|
|
'activity process power handles a package energy counter wrap'
|
|
)
|
|
|
|
const firstProcessPowerSample = activity.parseProcessSnapshot([
|
|
'schema\tactivity-processes\t1',
|
|
'sample\t400\t100\t10000',
|
|
'process\t10\t1000\tS\t100\t1000\t100\tbrowser',
|
|
''
|
|
].join('\n'))
|
|
const secondProcessPowerSample = activity.parseProcessSnapshot([
|
|
'schema\tactivity-processes\t1',
|
|
'sample\t402\t100\t10200',
|
|
'process\t10\t1000\tR\t100\t1100\t100\tbrowser',
|
|
''
|
|
].join('\n'))
|
|
const sampledPowerProcesses = activity.nextProcesses(
|
|
firstProcessPowerSample,
|
|
secondProcessPowerSample,
|
|
1000
|
|
)
|
|
assertEqual(secondProcessPowerSample.systemBusyTicks, 10200, 'activity parses aggregate system busy ticks')
|
|
assertEqual(sampledPowerProcesses[0].cpuTicksDelta, 100, 'activity retains a process CPU tick delta')
|
|
assertEqual(
|
|
activity.processSystemBusyDelta(firstProcessPowerSample, secondProcessPowerSample),
|
|
200,
|
|
'activity derives the matching aggregate system busy tick delta'
|
|
)
|
|
|
|
const processRows = [
|
|
{ pid: 10, name: 'browser', cpuTicksDelta: 100, memory: 10 },
|
|
{ pid: 20, name: 'music', cpuTicksDelta: 50, memory: 5 },
|
|
{ pid: 30, name: 'idle', cpuTicksDelta: 25, memory: 1 },
|
|
{ pid: 40, name: 'reset', cpuTicksDelta: -25, memory: 2 }
|
|
]
|
|
const estimatedPower = activity.estimateProcessPower(processRows, 10, 200)
|
|
assertEqual(estimatedPower[0].power, 5, 'activity allocates package power by system CPU ticks')
|
|
assertEqual(estimatedPower[1].power, 2.5, 'activity allocates a second proportional package power estimate')
|
|
assertEqual(estimatedPower[2].power, 1.25, 'activity allocates a third proportional package power estimate')
|
|
assertEqual(estimatedPower[3].power, 0, 'activity treats a negative CPU delta as no package share')
|
|
assertEqual(
|
|
estimatedPower.reduce((total, process) => total + process.power, 0),
|
|
8.75,
|
|
'activity leaves kernel and unobserved package power unallocated'
|
|
)
|
|
assert(
|
|
estimatedPower[0] !== processRows[0] && !Object.prototype.hasOwnProperty.call(processRows[0], 'power'),
|
|
'activity process power estimation does not mutate input rows'
|
|
)
|
|
assertDeepEqual(
|
|
activity.estimateProcessPower(processRows, -1, 200).map(process => process.power),
|
|
[-1, -1, -1, -1],
|
|
'activity marks process power unavailable without a package measurement'
|
|
)
|
|
assertDeepEqual(
|
|
activity.estimateProcessPower(processRows, 10, 0).map(process => process.power),
|
|
[-1, -1, -1, -1],
|
|
'activity marks process power unavailable across a zero busy interval'
|
|
)
|
|
assertDeepEqual(
|
|
activity.estimateProcessPower(processRows, 10, -50).map(process => process.power),
|
|
[-1, -1, -1, -1],
|
|
'activity marks process power unavailable after a system counter reset'
|
|
)
|
|
assertDeepEqual(
|
|
activity.filterAndSortProcesses(estimatedPower, '', 'power', false)
|
|
.map(process => process.pid),
|
|
[10, 20, 30, 40],
|
|
'activity sorts processes by estimated power'
|
|
)
|
|
const overAccountedPower = activity.estimateProcessPower([
|
|
{ pid: 1, cpuTicksDelta: 80 },
|
|
{ pid: 2, cpuTicksDelta: 60 }
|
|
], 10, 100)
|
|
assert(
|
|
Math.abs(overAccountedPower.reduce((total, process) => total + process.power, 0) - 10) < 1e-9,
|
|
'activity caps over-accounted process estimates at measured package power'
|
|
)
|
|
JS
|
|
|
|
fixture_root=$(mktemp -d)
|
|
trap 'rm -rf -- "$fixture_root"' EXIT
|
|
|
|
proc_path="$fixture_root/proc"
|
|
sys_path="$fixture_root/sys"
|
|
hwmon_path="$sys_path/class/hwmon/hwmon0"
|
|
powercap_path="$sys_path/class/powercap/intel-rapl"
|
|
package_path="$powercap_path/intel-rapl:0"
|
|
subzone_path="$powercap_path/intel-rapl:0:0"
|
|
gpu_devices="$fixture_root/gpu-devices"
|
|
gpu_drivers="$fixture_root/gpu-drivers"
|
|
cpu_policy0="$sys_path/devices/system/cpu/cpufreq/policy0"
|
|
cpu_policy1="$sys_path/devices/system/cpu/cpufreq/policy1"
|
|
intel_frequency="$gpu_devices/0000:00:02.0/tile0/gt0/freq0"
|
|
amd_hwmon="$gpu_devices/0000:03:00.0/hwmon/hwmon0"
|
|
|
|
mkdir -p \
|
|
"$proc_path/net" \
|
|
"$fixture_root/root filesystem" \
|
|
"$sys_path/class/block" \
|
|
"$sys_path/class/drm/card0" \
|
|
"$sys_path/class/drm/card1" \
|
|
"$sys_path/class/drm/card2" \
|
|
"$sys_path/class/net/eth0/device" \
|
|
"$cpu_policy0" \
|
|
"$cpu_policy1" \
|
|
"$gpu_devices/0000:00:02.0" \
|
|
"$gpu_devices/0000:03:00.0" \
|
|
"$gpu_devices/0000:04:00.0" \
|
|
"$gpu_drivers/xe" \
|
|
"$gpu_drivers/amdgpu" \
|
|
"$gpu_drivers/nvidia" \
|
|
"$intel_frequency" \
|
|
"$amd_hwmon" \
|
|
"$proc_path/500/fd" \
|
|
"$proc_path/500/fdinfo" \
|
|
"$proc_path/501/fd" \
|
|
"$proc_path/501/fdinfo" \
|
|
"$hwmon_path" \
|
|
"$package_path" \
|
|
"$subzone_path"
|
|
|
|
printf '321.50 100.00\n' >"$proc_path/uptime"
|
|
printf 'cpu 100 0 50 800 10 5 5 0 0 0\ncpu0 50 0 25 400 5 2 3 0 0 0\n' >"$proc_path/stat"
|
|
printf 'MemTotal: 1024 kB\nHugePages_Total: 0\nMemAvailable: 512 kB\nCached: 128 kB\nSReclaimable: 32 kB\nSwapTotal: 0 kB\nSwapFree: 0 kB\n' >"$proc_path/meminfo"
|
|
printf '0.50 0.25 0.10 1/100 123\n' >"$proc_path/loadavg"
|
|
printf 'Iface\tDestination\tGateway\tFlags\tRefCnt\tUse\tMetric\tMask\tMTU\tWindow\tIRTT\neth0\t00000000\t00000000\t0001\t0\t0\t25\t00000000\t0\t0\t0\n' \
|
|
>"$proc_path/net/route"
|
|
printf 'Inter-| Receive | Transmit\n face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed\neth0: 1000 1 0 0 0 0 0 0 2000 1 0 0 0 0 0 0\n' \
|
|
>"$proc_path/net/dev"
|
|
printf 'up\n' >"$sys_path/class/net/eth0/operstate"
|
|
printf 'DRIVER=test\n' >"$sys_path/class/net/eth0/device/uevent"
|
|
printf '2400000\n' >"$cpu_policy0/scaling_cur_freq"
|
|
printf '3200000\n' >"$cpu_policy1/scaling_cur_freq"
|
|
|
|
printf 'coretemp\n' >"$hwmon_path/name"
|
|
printf '62500\n' >"$hwmon_path/temp1_input"
|
|
printf 'Package id 0\n' >"$hwmon_path/temp1_label"
|
|
|
|
ln -s "$gpu_devices/0000:00:02.0" "$sys_path/class/drm/card0/device"
|
|
ln -s "$gpu_devices/0000:03:00.0" "$sys_path/class/drm/card1/device"
|
|
ln -s "$gpu_devices/0000:04:00.0" "$sys_path/class/drm/card2/device"
|
|
ln -s "$gpu_drivers/xe" "$gpu_devices/0000:00:02.0/driver"
|
|
ln -s "$gpu_drivers/amdgpu" "$gpu_devices/0000:03:00.0/driver"
|
|
ln -s "$gpu_drivers/nvidia" "$gpu_devices/0000:04:00.0/driver"
|
|
printf '0x8086\n' >"$gpu_devices/0000:00:02.0/vendor"
|
|
printf 'Intel Arc Fixture\n' >"$gpu_devices/0000:00:02.0/product_name"
|
|
printf '600\n' >"$intel_frequency/act_freq"
|
|
printf '0x1002\n' >"$gpu_devices/0000:03:00.0/vendor"
|
|
printf 'AMD Radeon Fixture\n' >"$gpu_devices/0000:03:00.0/product_name"
|
|
printf '47\n' >"$gpu_devices/0000:03:00.0/gpu_busy_percent"
|
|
printf '268435456\n' >"$gpu_devices/0000:03:00.0/mem_info_vram_used"
|
|
printf '2147483648\n' >"$gpu_devices/0000:03:00.0/mem_info_vram_total"
|
|
printf '1800000000\n' >"$amd_hwmon/freq1_input"
|
|
printf '0x10de\n' >"$gpu_devices/0000:04:00.0/vendor"
|
|
printf 'NVIDIA Fixture\n' >"$gpu_devices/0000:04:00.0/product_name"
|
|
|
|
printf '%s\n' \
|
|
$'drm-driver:\txe' \
|
|
$'drm-client-id:\t77' \
|
|
$'drm-pdev:\t0000:00:02.0' \
|
|
$'drm-resident-gtt:\t64 MiB' \
|
|
$'drm-cycles-rcs:\t100' \
|
|
$'drm-total-cycles-rcs:\t1000' \
|
|
>"$proc_path/500/fdinfo/5"
|
|
cp "$proc_path/500/fdinfo/5" "$proc_path/500/fdinfo/6"
|
|
ln -s /dev/dri/renderD128 "$proc_path/500/fd/5"
|
|
ln -s /dev/dri/renderD128 "$proc_path/500/fd/6"
|
|
printf '%s\n' \
|
|
$'drm-driver:\txe' \
|
|
$'drm-client-id:\t78' \
|
|
$'drm-pdev:\t0000:00:02.0' \
|
|
$'drm-resident-gtt:\t32 MiB' \
|
|
$'drm-cycles-rcs:\t50' \
|
|
$'drm-total-cycles-rcs:\t1000' \
|
|
>"$proc_path/501/fdinfo/8"
|
|
ln -s /dev/dri/renderD128 "$proc_path/501/fd/8"
|
|
|
|
printf '4096\t1000\t250\t200\n' >"$fixture_root/statfs"
|
|
printf '00000000:04:00.0\tNVIDIA GeForce Fixture\t62\t2147483648\t8589934592\t1950\n' \
|
|
>"$fixture_root/nvidia.tsv"
|
|
printf 'E:MEMORY_DEVICE_0_SPEED_MTS=7200\nE:MEMORY_DEVICE_0_CONFIGURED_SPEED_GTS=6.4\n' \
|
|
>"$fixture_root/udev-data"
|
|
|
|
storage_snapshot=$(
|
|
OMARCHY_SYSTEM_STATS_PROC_PATH="$proc_path" \
|
|
OMARCHY_SYSTEM_STATS_ROOT_PATH="$fixture_root/root filesystem" \
|
|
OMARCHY_SYSTEM_STATS_STATFS_FIXTURE_PATH="$fixture_root/statfs" \
|
|
"$ROOT/activity-stats" --activity-storage
|
|
)
|
|
expected_storage=$'schema\tactivity-storage\t1\nsample\t321.50\nstorage\t/\t4096000\t3072000\t819200'
|
|
[[ $storage_snapshot == "$expected_storage" ]] ||
|
|
fail "activity root storage output is exact, versioned, and byte-based" "$storage_snapshot"
|
|
pass "activity root storage output is exact, versioned, and byte-based"
|
|
|
|
printf '%s\n' \
|
|
$'/data 4096 2000 100 50' \
|
|
$'/ 4096 1000 250 200' \
|
|
>"$fixture_root/statfs-multi"
|
|
multi_storage_snapshot=$(
|
|
OMARCHY_SYSTEM_STATS_PROC_PATH="$proc_path" \
|
|
OMARCHY_SYSTEM_STATS_ROOT_PATH="$fixture_root/root filesystem" \
|
|
OMARCHY_SYSTEM_STATS_STATFS_FIXTURE_PATH="$fixture_root/statfs-multi" \
|
|
"$ROOT/activity-stats" --activity-storage
|
|
)
|
|
expected_multi_storage=$'schema\tactivity-storage\t1\nsample\t321.50\nstorage\t/\t4096000\t3072000\t819200\nstorage\t/data\t8192000\t7782400\t204800'
|
|
[[ $multi_storage_snapshot == "$expected_multi_storage" ]] ||
|
|
fail "activity storage output lists local volumes in stable order" "$multi_storage_snapshot"
|
|
pass "activity storage output lists local volumes in stable order"
|
|
|
|
live_storage=$("$ROOT/activity-stats" --activity-storage)
|
|
grep -Eq $'^storage\t/' <<<"$live_storage" ||
|
|
fail "activity live storage does not include the root filesystem" "$live_storage"
|
|
if findmnt -n -o FSTYPE /tmp >/dev/null 2>&1 && [[ $(findmnt -n -o FSTYPE /tmp) == tmpfs ]]; then
|
|
if grep -Eq $'^storage\t/tmp\t' <<<"$live_storage"; then
|
|
fail "activity live storage includes tmpfs" "$live_storage"
|
|
fi
|
|
fi
|
|
root_source=$(findmnt -n -o SOURCE / 2>/dev/null || true)
|
|
home_source=$(findmnt -n -o SOURCE /home 2>/dev/null || true)
|
|
if [[ -n $home_source && $home_source == "$root_source" ]] &&
|
|
grep -Eq $'^storage\t/home\t' <<<"$live_storage"; then
|
|
fail "activity storage splits subvolumes of the same device" "$live_storage"
|
|
fi
|
|
pass "activity live storage stays on local filesystems"
|
|
|
|
thermal_snapshot=$(
|
|
OMARCHY_SYSTEM_STATS_PROC_PATH="$proc_path" \
|
|
OMARCHY_SYSTEM_STATS_SYS_PATH="$sys_path" \
|
|
"$ROOT/activity-stats" --activity-thermals
|
|
)
|
|
expected_thermals=$'schema\tactivity-thermals\t1\nsample\t321.50\ntemperature\thwmon0/temp1\tcoretemp\tPackage id 0\t62500'
|
|
[[ $thermal_snapshot == "$expected_thermals" ]] ||
|
|
fail "activity CPU thermal output is exact and versioned" "$thermal_snapshot"
|
|
pass "activity CPU thermal output is exact and versioned"
|
|
|
|
gpu_snapshot=$(
|
|
OMARCHY_SYSTEM_STATS_PROC_PATH="$proc_path" \
|
|
OMARCHY_SYSTEM_STATS_SYS_PATH="$sys_path" \
|
|
OMARCHY_SYSTEM_STATS_NVIDIA_FIXTURE_PATH="$fixture_root/nvidia.tsv" \
|
|
"$ROOT/activity-stats" --activity-gpus
|
|
)
|
|
grep -Fxq $'schema\tactivity-gpus\t1' <<<"$gpu_snapshot" ||
|
|
fail "activity GPU output has its own schema"
|
|
grep -Fxq $'gpu\t0000:00:02.0\tIntel\txe\tIntel Arc Fixture\t-1\t-1\t-1\tunknown\t600' \
|
|
<<<"$gpu_snapshot" ||
|
|
fail "activity GPU output reads an Intel/Xe graphics clock"
|
|
grep -Fxq $'gpu\t0000:03:00.0\tAMD\tamdgpu\tAMD Radeon Fixture\t47\t268435456\t2147483648\tvram\t1800' \
|
|
<<<"$gpu_snapshot" ||
|
|
fail "activity GPU output reads AMD busy, VRAM, and graphics clock counters"
|
|
grep -Fxq $'gpu\t0000:04:00.0\tNVIDIA\tnvidia\tNVIDIA GeForce Fixture\t62\t2147483648\t8589934592\tvram\t1950' \
|
|
<<<"$gpu_snapshot" ||
|
|
fail "activity GPU output reads NVIDIA utilization, VRAM, and graphics clock through persistent NVML"
|
|
grep -Fxq $'gpu-memory\t0000:00:02.0\tshared\t100663296' <<<"$gpu_snapshot" ||
|
|
fail "activity GPU output does not aggregate shared DRM memory"
|
|
[[ $(grep -c $'^engine\t0000:00:02.0\t77\trcs\t' <<<"$gpu_snapshot") -eq 1 ]] ||
|
|
fail "activity GPU output double-counts duplicated DRM file descriptors"
|
|
[[ $(grep -c $'^engine\t0000:00:02.0\t78\trcs\t' <<<"$gpu_snapshot") -eq 1 ]] ||
|
|
fail "activity GPU output loses a distinct DRM client"
|
|
pass "activity GPU collector supports shared Intel, AMD, NVIDIA, and multiple adapters"
|
|
|
|
read_gpu_frame() {
|
|
local output_fd="$1"
|
|
local result_name="$2"
|
|
local line
|
|
local -n result="$result_name"
|
|
result=""
|
|
|
|
while IFS= read -r -u "$output_fd" line; do
|
|
[[ $line == $'snapshot-end\tgpus' ]] && return 0
|
|
result+="$line"$'\n'
|
|
done
|
|
return 1
|
|
}
|
|
|
|
coproc GPU_READER {
|
|
exec env \
|
|
OMARCHY_SYSTEM_STATS_PROC_PATH="$proc_path" \
|
|
OMARCHY_SYSTEM_STATS_SYS_PATH="$sys_path" \
|
|
OMARCHY_SYSTEM_STATS_NVIDIA_FIXTURE_PATH="$fixture_root/nvidia.tsv" \
|
|
OMARCHY_SYSTEM_STATS_GPU_DISCOVERY_INTERVAL_MS=500 \
|
|
"$ROOT/activity-sampler" --activity-reader
|
|
}
|
|
gpu_reader_pid=$GPU_READER_PID
|
|
gpu_reader_input=${GPU_READER[1]}
|
|
gpu_reader_output=${GPU_READER[0]}
|
|
printf 'gpus\n' >&"$gpu_reader_input"
|
|
read_gpu_frame "$gpu_reader_output" first_gpu_frame ||
|
|
fail "activity GPU reader stopped before its first frame"
|
|
|
|
mkdir -p "$proc_path/502/fd" "$proc_path/502/fdinfo"
|
|
printf '%s\n' \
|
|
$'drm-driver:\txe' \
|
|
$'drm-client-id:\t79' \
|
|
$'drm-pdev:\t0000:00:02.0' \
|
|
$'drm-resident-gtt:\t16 MiB' \
|
|
$'drm-cycles-rcs:\t25' \
|
|
$'drm-total-cycles-rcs:\t1000' \
|
|
>"$proc_path/502/fdinfo/9"
|
|
ln -s /dev/dri/renderD128 "$proc_path/502/fd/9"
|
|
|
|
printf 'gpus\n' >&"$gpu_reader_input"
|
|
read_gpu_frame "$gpu_reader_output" cached_gpu_frame ||
|
|
fail "activity GPU reader stopped during its cached frame"
|
|
if grep -Fq $'engine\t0000:00:02.0\t79\t' <<<"$cached_gpu_frame"; then
|
|
fail "activity GPU reader rescanned every process file descriptor on every sample"
|
|
fi
|
|
|
|
sleep 0.55
|
|
printf 'gpus\n' >&"$gpu_reader_input"
|
|
read_gpu_frame "$gpu_reader_output" rediscovered_gpu_frame ||
|
|
fail "activity GPU reader stopped before its discovery refresh"
|
|
exec {gpu_reader_input}>&-
|
|
wait "$gpu_reader_pid"
|
|
grep -Fq $'engine\t0000:00:02.0\t79\trcs\t25\t1000\t1\tcycles' \
|
|
<<<"$rediscovered_gpu_frame" ||
|
|
fail "activity GPU reader did not discover a new client after its cache interval"
|
|
pass "activity GPU reader refreshes counters without rescanning process FDs every sample"
|
|
|
|
read_thermal_frame() {
|
|
local output_fd="$1"
|
|
local result_name="$2"
|
|
local line
|
|
local -n result="$result_name"
|
|
result=""
|
|
|
|
while IFS= read -r -u "$output_fd" line; do
|
|
[[ $line == $'snapshot-end\tthermals' ]] && return 0
|
|
result+="$line"$'\n'
|
|
done
|
|
return 1
|
|
}
|
|
|
|
coproc THERMAL_READER {
|
|
exec env \
|
|
OMARCHY_SYSTEM_STATS_PROC_PATH="$proc_path" \
|
|
OMARCHY_SYSTEM_STATS_SYS_PATH="$sys_path" \
|
|
"$ROOT/activity-stats" --activity-reader
|
|
}
|
|
thermal_reader_pid=$THERMAL_READER_PID
|
|
thermal_reader_input=${THERMAL_READER[1]}
|
|
thermal_reader_output=${THERMAL_READER[0]}
|
|
printf 'thermals\n' >&"$thermal_reader_input"
|
|
read_thermal_frame "$thermal_reader_output" first_thermal_frame ||
|
|
fail "activity thermal reader stopped before its first frame"
|
|
grep -Fq $'temperature\thwmon0/temp1\tcoretemp\tPackage id 0\t62500' \
|
|
<<<"$first_thermal_frame" ||
|
|
fail "activity thermal reader did not cache its selected CPU sensor"
|
|
|
|
rm -- "$hwmon_path/temp1_input"
|
|
fallback_hwmon_path="$sys_path/class/hwmon/hwmon1"
|
|
mkdir -p "$fallback_hwmon_path"
|
|
printf 'k10temp\n' >"$fallback_hwmon_path/name"
|
|
printf '55000\n' >"$fallback_hwmon_path/temp1_input"
|
|
printf 'Tctl\n' >"$fallback_hwmon_path/temp1_label"
|
|
printf 'thermals\n' >&"$thermal_reader_input"
|
|
read_thermal_frame "$thermal_reader_output" second_thermal_frame ||
|
|
fail "activity thermal reader stopped before its fallback frame"
|
|
exec {thermal_reader_input}>&-
|
|
wait "$thermal_reader_pid"
|
|
grep -Fq $'temperature\thwmon1/temp1\tk10temp\tTctl\t55000' \
|
|
<<<"$second_thermal_frame" ||
|
|
fail "activity thermal reader did not rescan after its cached sensor vanished"
|
|
pass "activity thermal reader caches and safely invalidates its CPU sensor"
|
|
|
|
resource_snapshot=$(
|
|
OMARCHY_SYSTEM_STATS_PROC_PATH="$proc_path" \
|
|
OMARCHY_SYSTEM_STATS_SYS_PATH="$sys_path" \
|
|
OMARCHY_SYSTEM_STATS_UDEV_DATA_PATH="$fixture_root/udev-data" \
|
|
"$ROOT/activity-stats" --activity-resources
|
|
)
|
|
grep -Fxq $'schema\tactivity-resources\t1' <<<"$resource_snapshot" ||
|
|
fail "activity resources output has its own schema"
|
|
grep -Fxq $'memory\t1024\t512\t0\t0\t160' <<<"$resource_snapshot" ||
|
|
fail "activity resources output includes page cache and reclaimable slabs"
|
|
grep -Fxq $'frequency\tcpu\t2800\tMHz' <<<"$resource_snapshot" ||
|
|
fail "activity resources output averages current CPU policy frequencies"
|
|
grep -Fxq $'frequency\tmemory\t6400\tMT/s' <<<"$resource_snapshot" ||
|
|
fail "activity resources output reads the configured DDR transfer rate"
|
|
grep -Fxq $'network\teth0\t1000\t2000\tup\t1\t1' <<<"$resource_snapshot" ||
|
|
fail "activity resources do not follow an up default route without a gateway"
|
|
if grep -q '^temperature' <<<"$resource_snapshot"; then
|
|
fail "activity resources output avoids detailed sensor reads"
|
|
fi
|
|
pass "activity resources output avoids detailed sensor reads"
|
|
|
|
rm -- "$proc_path/net/route"
|
|
resource_without_route=$(
|
|
OMARCHY_SYSTEM_STATS_PROC_PATH="$proc_path" \
|
|
OMARCHY_SYSTEM_STATS_SYS_PATH="$sys_path" \
|
|
OMARCHY_SYSTEM_STATS_UDEV_DATA_PATH="$fixture_root/udev-data" \
|
|
"$ROOT/activity-stats" --activity-resources
|
|
)
|
|
grep -Fxq $'schema\tactivity-resources\t1' <<<"$resource_without_route" ||
|
|
fail "activity resources fail when optional route metadata is absent"
|
|
pass "activity resources tolerate missing optional procfs inputs"
|
|
|
|
process_snapshot=$(
|
|
OMARCHY_SYSTEM_STATS_PROC_PATH="$proc_path" \
|
|
OMARCHY_SYSTEM_STATS_PASSWD_PATH="$fixture_root/missing-passwd" \
|
|
OMARCHY_SYSTEM_STATS_CLOCK_TICKS=100 \
|
|
"$ROOT/activity-stats" --activity-processes
|
|
)
|
|
expected_process_snapshot=$'schema\tactivity-processes\t1\nsample\t321.50\t100\t160'
|
|
[[ $process_snapshot == "$expected_process_snapshot" ]] ||
|
|
fail "activity process output includes exact aggregate busy ticks" "$process_snapshot"
|
|
pass "activity process output includes exact aggregate busy ticks"
|
|
|
|
printf 'package-0\n' >"$package_path/name"
|
|
printf '9000000\n' >"$package_path/energy_uj"
|
|
printf '10000000\n' >"$package_path/max_energy_range_uj"
|
|
printf 'core\n' >"$subzone_path/name"
|
|
printf '0\n' >"$subzone_path/enabled"
|
|
printf '1000000\n' >"$subzone_path/energy_uj"
|
|
printf '10000000\n' >"$subzone_path/max_energy_range_uj"
|
|
|
|
psys_path="$powercap_path/intel-rapl:1"
|
|
non_package_path="$powercap_path/intel-rapl:2"
|
|
uncore_path="$powercap_path/intel-rapl:0:1"
|
|
dram_path="$powercap_path/intel-rapl:0:2"
|
|
mmio_package_path="$sys_path/class/powercap/intel-rapl-mmio/intel-rapl-mmio:0"
|
|
mkdir -p "$psys_path" "$non_package_path" "$uncore_path" "$dram_path" "$mmio_package_path"
|
|
printf 'psys\n' >"$psys_path/name"
|
|
printf '8000000\n' >"$psys_path/energy_uj"
|
|
printf '10000000\n' >"$psys_path/max_energy_range_uj"
|
|
printf 'dram\n' >"$non_package_path/name"
|
|
printf '7000000\n' >"$non_package_path/energy_uj"
|
|
printf '10000000\n' >"$non_package_path/max_energy_range_uj"
|
|
printf 'uncore\n' >"$uncore_path/name"
|
|
printf '6000000\n' >"$uncore_path/energy_uj"
|
|
printf '10000000\n' >"$uncore_path/max_energy_range_uj"
|
|
printf 'dram\n' >"$dram_path/name"
|
|
printf '5000000\n' >"$dram_path/energy_uj"
|
|
printf '10000000\n' >"$dram_path/max_energy_range_uj"
|
|
printf 'package-0\n' >"$mmio_package_path/name"
|
|
printf '8000000\n' >"$mmio_package_path/energy_uj"
|
|
printf '10000000\n' >"$mmio_package_path/max_energy_range_uj"
|
|
|
|
process_power_snapshot=$(
|
|
OMARCHY_SYSTEM_STATS_PROC_PATH="$proc_path" \
|
|
OMARCHY_SYSTEM_STATS_SYS_PATH="$sys_path" \
|
|
"$ROOT/activity-stats" --activity-process-power
|
|
)
|
|
expected_process_power=$'schema\tactivity-process-power\t1\nsample\t321.50\npackage\tintel-rapl:0\tpackage-0\t9000000\t10000000'
|
|
[[ $process_power_snapshot == "$expected_process_power" ]] ||
|
|
fail "activity process power output is exact and includes only the RAPL package domain" "$process_power_snapshot"
|
|
pass "activity process power output is exact and includes only the RAPL package domain"
|
|
|
|
process_power_reader_snapshot=$(
|
|
printf 'ignored\nsample\nsample\n' |
|
|
OMARCHY_SYSTEM_STATS_PROC_PATH="$proc_path" \
|
|
OMARCHY_SYSTEM_STATS_SYS_PATH="$sys_path" \
|
|
"$ROOT/activity-stats" --activity-process-power-reader
|
|
)
|
|
[[ $(grep -c '^snapshot-end' <<<"$process_power_reader_snapshot") -eq 2 ]] ||
|
|
fail "activity process power reader does not frame each requested sample"
|
|
[[ $(grep -c '^package' <<<"$process_power_reader_snapshot") -eq 2 ]] ||
|
|
fail "activity process power reader does not reuse its process for requested samples"
|
|
pass "activity process power reader serves framed samples until its input closes"
|