25 Commits
Author SHA1 Message Date
Mikhail Yevchenko 7c06a8089d Add mixed-vendor GPU summary and fix startup heap corruption
Collect AMD amdgpu sysfs and optional NVIDIA nvidia-smi stats in the
background, auto-detecting supported adapters in PCI order and rotating
additional pairs alongside CPU/RAM. Explicit PCI selection and off mode
remain supported; missing metrics render as N/A.

Initialize the bitmap-only canvas locally instead of the ABI-dependent
libg15render initializer: the installed TTF-enabled renderer wrote an
8-byte FreeType pointer past the smaller bitmap-only allocation, corrupting
the heap before GPU threads started. Add collector, rendering and full-main
regression tests.
2026-09-07 17:06:32 +00:00
Mikhail Yevchenko 076641c867 Add Debian 13 packaging for g15stats 2026-09-07 16:13:47 +03:00
Daniel Menelkir 95de11a526 Update README.rst 2024-06-09 14:57:20 +00:00
Daniel Menelkir c0ca57d355 Update README.rst 2022-08-05 19:13:31 +00:00
Daniel Menelkir c1a212eb1e Update email 2022-05-14 17:04:55 -03:00
Daniel Menelkir 025a5cd5ba modified: contrib/init/g15stats.66
modified:   contrib/init/g15stats.confd
	modified:   contrib/init/g15stats.in
	modified:   contrib/init/g15stats.initd
	modified:   contrib/init/g15stats.run
	modified:   contrib/init/g15stats.service
	modified:   contrib/init/rc.g15stats
	deleted:    dists/arch/g15stats-openrc/.SRCINFO
	deleted:    dists/arch/g15stats-openrc/PKGBUILD
	deleted:    dists/arch/g15stats-runit/.SRCINFO
	deleted:    dists/arch/g15stats-runit/PKGBUILD
	deleted:    dists/arch/g15stats-systemd/.SRCINFO
	deleted:    dists/arch/g15stats-systemd/PKGBUILD
	deleted:    dists/arch/g15stats/.SRCINFO
	deleted:    dists/arch/g15stats/PKGBUILD
	deleted:    dists/freebsd/Makefile
	deleted:    dists/freebsd/distinfo
	deleted:    dists/freebsd/files/g15stats.in
	deleted:    dists/freebsd/pkg-descr
	deleted:    dists/freebsd/pkg-plist
	deleted:    dists/gentoo/Manifest
	deleted:    dists/gentoo/g15stats-3.0.2.ebuild
	deleted:    dists/gentoo/g15stats-9999.ebuild
	deleted:    dists/gentoo/metadata.xml
	deleted:    dists/void/files/run
	deleted:    dists/void/template
2021-11-01 08:31:49 -03:00
Daniel Menelkir 7c7fd3b605 modified: config/ltmain.sh
modified:   contrib/init/g15stats.initd
	modified:   contrib/init/g15stats.run
	modified:   contrib/init/rc.g15stats
	modified:   dist/arch/g15stats-openrc/.SRCINFO
	modified:   dist/arch/g15stats-openrc/PKGBUILD
	modified:   dist/arch/g15stats-runit/.SRCINFO
	modified:   dist/arch/g15stats-runit/PKGBUILD
	modified:   dist/arch/g15stats-systemd/.SRCINFO
	modified:   dist/arch/g15stats-systemd/PKGBUILD
2021-11-01 08:30:48 -03:00
Daniel Menelkir e96d818469 Merge branch 'Marzal-master-patch-15671' into 'master'
g15stats.service wont start without g15daemon.service installed

See merge request menelkir/g15stats!1
2021-11-01 10:16:50 +00:00
David Marzal a906005a87 Update dist/arch/g15stats-systemd/PKGBUILD 2021-10-31 22:17:10 +00:00
David Marzal 05ca759c7b g15stats.service wont start without g15daemon.service installed 2021-10-31 22:10:57 +00:00
Daniel Menelkir b749ed8ad5 new file: contrib/init/rc.g15stats 2021-07-23 08:02:08 -03:00
Daniel Menelkir 9dda88635a Fixed openrc script 2021-06-13 04:55:52 -03:00
Daniel Menelkir 7ad83e1529 Updated distro files 2021-06-08 20:27:27 -03:00
Daniel Menelkir ee4adb969b modified: contrib/init/g15stats.run 2021-06-05 04:24:39 -03:00
Daniel Menelkir 2e4ba7177a Added void template 2021-05-22 11:11:47 -03:00
Daniel Menelkir cef3398d88 Merge branch 'master' of gitlab.com:menelkir/g15stats 2021-05-14 20:11:06 -03:00
Daniel Menelkir 9f0f01ed9e Update config files 2021-05-14 20:10:49 -03:00
Daniel Menelkir b12478b3b8 Added 66 service 2021-05-10 19:15:55 -03:00
Daniel Menelkir 1c3f49f905 renamed: dists/g15stats/Makefile -> dists/freebsd/Makefile
renamed:    dists/g15stats/distinfo -> dists/freebsd/distinfo
	renamed:    dists/g15stats/files/g15stats.in -> dists/freebsd/files/g15stats.in
	renamed:    dists/g15stats/pkg-descr -> dists/freebsd/pkg-descr
	renamed:    dists/g15stats/pkg-plist -> dists/freebsd/pkg-plist
2021-05-05 18:55:08 -03:00
Daniel Menelkir 2ba45c8e9a new file: contrib/init/g15stats.in
new file:   dists/g15stats/Makefile
	new file:   dists/g15stats/distinfo
	new file:   dists/g15stats/files/g15stats.in
	new file:   dists/g15stats/pkg-descr
	new file:   dists/g15stats/pkg-plist
2021-05-05 18:54:25 -03:00
Daniel Menelkir bd01f2ab24 Added support for distributions 2021-05-05 18:51:03 -03:00
Daniel Menelkir 3297bfbdf1 Adequate init files in contrib to reflect gentoo display-manager 2021-04-25 07:21:46 -03:00
Daniel Menelkir 0ec5241751 modified: configure.ac 2021-03-20 13:53:32 -03:00
Daniel Menelkir d947d80dcc modified: Makefile.in 2021-03-10 17:27:30 -03:00
Daniel Menelkir ea338d176a modified: ChangeLog
modified:   Makefile.am
	modified:   Makefile.in
	modified:   aclocal.m4
	deleted:    autogen.sh
	modified:   configure
	renamed:    configure.in -> configure.ac
2021-03-10 17:23:22 -03:00
63 changed files with 2372 additions and 254 deletions

No files matched your search

+1 -1
View File
@@ -5,4 +5,4 @@ Antonio Bartolini <robynhub@gmail.com>
Piotr Czarnecki <czarnyckm@users.sf.net> Piotr Czarnecki <czarnyckm@users.sf.net>
Daniel Menelkir <menelkir@itroll.org> Daniel Menelkir <dmenelkir@gmail.com>
-84
View File
@@ -1,84 +0,0 @@
0.1 Initial release
1.0
- Change net scale from absolute to relative, with option to revert to
absolute (-nsa --net-scale-absolute).
SVN:
- Improve response time when switching screens.
- Battery status monitoring courtesy of Pieter De Wit
- Detect if Network interface is incorrect and disable netscreen. Resolves 100% cpu issue reported by Guy Dierx.
SVN 504:
- Bottom row information rotation switched with the L5 key
- 3 additional CPU graph variants switched with the L4 key (at this moment for multi core processors only)
- New default CPU Screen (at this moment for multi core processors only)
- Display unicore graphs only on the CPU screen with the option -u (--unicore)
SVN 505:
- Bugfix: Render bottom line on the Memory Screen once
SVN 509:
- Temperature sensors depends on the lmsensors sysfs filesystem
SVN 510:
- Temperature sensor's autodetect
- Force user sensor id with the option -s id (The id should point to sysfs path /sys/class/hwmon/hwmon[id]/device/temp1_input)
SVN 511:
- Improve sensor's autodetect
- Clean up
SVN 512:
- CPU Screens variants for uni core processors
- CPU frequency info (if available)
- Battery Screen is enabled if available
- Clean up
SVN 513:
- Improve bottom line and keywatch logic
SVN 514:
- Many fixes, clean up
- Changed version to 1.90
- CPU Frequency use proper sysfs variable
- New Summary Screen indicators
- Network current traffic info in the bottom line
- Refactor CPU variants into separates additional screens: Summary, CPU and CPU Freq.
- New Fan Speed Screen
- After change screen cycle the info bar content is set to the screen default one.
- Net Screen scaling is now controlled with the L4 key
- Force start temperature sensor id with the option -t id (The id should point to sysfs path /sys/class/hwmon/hwmon[id]/...)
- Force start fan speed sensor id with the option -f id (The id should point to sysfs path /sys/class/hwmon/hwmon[id]/...)
- Force to show temperature [id] in place of the maximal one on the Summary Screen with the option ie -gt 1
(the id should point to sysfs path /sys/class/hwmon/hwmon../device/temp[id]_input)
- On the Fan / Temp Screen Current sensor is controlled with the L4 key (on systems with more then one sensor only).
- Every Screen has his own mode now (except Swap, Memory and Battery Screen)
SVN 515 (1.90):
- Fixed temperature / fan sensor lost
- Fixed screen cycle
SVN 516:
- Refactored temperature and fan speed - share the logic
SVN 517:
- Improve Bat Screen deactivation logic (now is it similar to SVN 515: Fixed temperature / fan sensor lost)
SVN 518 (1.91):
- Fix Summary, Freq and CPU Screens for some variants of the cpu cores number
- Improve Summary Screen
SVN 519
- Change default mode for Freq and Summary Screen
- Improve Summary Screen for cpus with more then 7 cores
SVN 520 (1.9.2)
- Disable monitoring CPUs frequencies with the option -df
- Improve Summary Screen without CPUs frequencies
SVN 530 (1.9.3)
- Activate the bottom info bar content rotate with the option -ir
- Change bottom info bar content to the component default one
- Activate variable CPUs count with the option -vc
- Improve Fan bottom info
SVN 531 (1.9.4)
- Fix time info
- Improve Fan / Temperature Screens leading labels position
- Improve Fan / Temperature Screens height calculation
- Improve Summary Screen multi bar (temperature and network)
SVN 532 (1.9.5)
- Improve Summary Screen multi bar (temperature and network)
- Fix compilation with the newer version of the glibc
- Improve refresh interval causing -nan error with the newer kernel on very fast machines due to too often glibtop probing
- Fix possible -nan error on the fast screens switch
- Change refresh interval with the option -r seconds (The seconds must be between 1 and 300)
SVN 534 (1.9.6)
- Improve Summary Screen with 3 and 6 core CPU
SVN 538 (1.9.7)
- Improve Summary Screen Fan / Temperature sensors bar calculation
1.9.8
- Fork and some fixes, added scripts from gentoo.
+14 -1
View File
@@ -1,5 +1,5 @@
AM_CFLAGS = -O3 -Wall @CFLAGS@ -I$(top_builddir) -I$(top_srcdir) AM_CFLAGS = -Wall @CFLAGS@ -I$(top_builddir) -I$(top_srcdir)
METASOURCES = AUTO METASOURCES = AUTO
bin_PROGRAMS = g15stats bin_PROGRAMS = g15stats
noinst_HEADERS = g15stats.h noinst_HEADERS = g15stats.h
@@ -11,5 +11,18 @@ LIBS = -lg15daemon_client -lg15render -lpthread -lm -lrt
IMCLUDES = -I IMCLUDES = -I
g15stats_LDFLAGS = -avoid-version g15stats_LDFLAGS = -avoid-version
g15stats_SOURCES = g15stats.c g15stats.h g15stats_SOURCES = g15stats.c g15stats.h
EXTRA_DIST = gpu.h tests/gpu-test.c tests/render-test.c tests/runtime-test.c tests/render-layouts.sh tests/render-images.sh
noinst_HEADERS += gpu.h
AUTOMAKE_OPTIONS = subdir-objects
check_PROGRAMS = gpu-test render-test runtime-test
TESTS = $(check_PROGRAMS) tests/render-layouts.sh
TEST_EXTENSIONS = .sh
SH_LOG_COMPILER = $(SHELL)
gpu_test_SOURCES = tests/gpu-test.c
gpu_test_LDADD = -lpthread
render_test_SOURCES = tests/render-test.c
render_test_LDADD = -lg15daemon_client -lg15render -lpthread -lm -lrt $(GTOP_LIBS)
runtime_test_SOURCES = tests/runtime-test.c
runtime_test_LDADD = -lg15render -lpthread -lm -lrt $(GTOP_LIBS)
+6 -4
View File
@@ -1,4 +1,4 @@
# Makefile.in generated by automake 1.16.2 from Makefile.am. # Makefile.in generated by automake 1.16.3 from Makefile.am.
# @configure_input@ # @configure_input@
# Copyright (C) 1994-2020 Free Software Foundation, Inc. # Copyright (C) 1994-2020 Free Software Foundation, Inc.
@@ -93,7 +93,7 @@ host_triplet = @host@
bin_PROGRAMS = g15stats$(EXEEXT) bin_PROGRAMS = g15stats$(EXEEXT)
subdir = . subdir = .
ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
am__aclocal_m4_deps = $(top_srcdir)/configure.in am__aclocal_m4_deps = $(top_srcdir)/configure.ac
am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
$(ACLOCAL_M4) $(ACLOCAL_M4)
DIST_COMMON = $(srcdir)/Makefile.am $(top_srcdir)/configure \ DIST_COMMON = $(srcdir)/Makefile.am $(top_srcdir)/configure \
@@ -230,6 +230,8 @@ am__post_remove_distdir = $(am__remove_distdir)
DIST_ARCHIVES = $(distdir).tar.gz DIST_ARCHIVES = $(distdir).tar.gz
GZIP_ENV = --best GZIP_ENV = --best
DIST_TARGETS = dist-gzip DIST_TARGETS = dist-gzip
# Exists only to be overridden by the user if desired.
AM_DISTCHECK_DVI_TARGET = dvi
distuninstallcheck_listfiles = find . -type f -print distuninstallcheck_listfiles = find . -type f -print
am__distuninstallcheck_listfiles = $(distuninstallcheck_listfiles) \ am__distuninstallcheck_listfiles = $(distuninstallcheck_listfiles) \
| sed 's|^\./|$(prefix)/|' | grep -v '$(infodir)/dir$$' | sed 's|^\./|$(prefix)/|' | grep -v '$(infodir)/dir$$'
@@ -354,7 +356,7 @@ target_alias = @target_alias@
top_build_prefix = @top_build_prefix@ top_build_prefix = @top_build_prefix@
top_builddir = @top_builddir@ top_builddir = @top_builddir@
top_srcdir = @top_srcdir@ top_srcdir = @top_srcdir@
AM_CFLAGS = -O3 -Wall @CFLAGS@ -I$(top_builddir) -I$(top_srcdir) AM_CFLAGS = -Wall @CFLAGS@ -I$(top_builddir) -I$(top_srcdir)
METASOURCES = AUTO METASOURCES = AUTO
noinst_HEADERS = g15stats.h noinst_HEADERS = g15stats.h
doc_DATA = README ChangeLog AUTHORS NEWS COPYING doc_DATA = README ChangeLog AUTHORS NEWS COPYING
@@ -713,7 +715,7 @@ distcheck: dist
$(DISTCHECK_CONFIGURE_FLAGS) \ $(DISTCHECK_CONFIGURE_FLAGS) \
--srcdir=../.. --prefix="$$dc_install_base" \ --srcdir=../.. --prefix="$$dc_install_base" \
&& $(MAKE) $(AM_MAKEFLAGS) \ && $(MAKE) $(AM_MAKEFLAGS) \
&& $(MAKE) $(AM_MAKEFLAGS) dvi \ && $(MAKE) $(AM_MAKEFLAGS) $(AM_DISTCHECK_DVI_TARGET) \
&& $(MAKE) $(AM_MAKEFLAGS) check \ && $(MAKE) $(AM_MAKEFLAGS) check \
&& $(MAKE) $(AM_MAKEFLAGS) install \ && $(MAKE) $(AM_MAKEFLAGS) install \
&& $(MAKE) $(AM_MAKEFLAGS) installcheck \ && $(MAKE) $(AM_MAKEFLAGS) installcheck \
+107
View File
@@ -2,6 +2,113 @@ G15Stats - a CPU/Memory/Swap/Network/Battery/Temperature/Fan Speed/CPU Frequenci
The G15Stats requires libgtop development packages to be installed before compilation. The G15Stats requires libgtop development packages to be installed before compilation.
GPU summary columns:
The default --gpu auto collects ALL PCI devices (sorted PCI address) bound
to amdgpu or nvidia at startup, including mixed vendors. Use --gpu dddd:bb:ss.f (also -gpu), for example
--gpu 0000:01:00.0, to select a particular card. --gpu off restores the original
layout and disables collection. An explicit missing/unsupported card shows N/A;
auto with no supported card leaves the original layout unchanged. Restart to
rediscover cards after hotplug. Intel and the nouveau/radeon drivers are not
supported. Missing tools, permissions or individual metrics produce N/A, not 0.
With one GPU the 160x43 summary retains its existing 4/5 indicator rows and
labels. Graphs end at x=96 instead of 153; TOTAL moves to x=98. A divider
at x=105 separates the column at x=108..159. With two or more GPUs the left
x=0..50 shows compact CPU percent, R used system RAM, T total system RAM,
SWP percent, and (in five-row mode) GPU count. Four-row mode uses y=1,10,19,28;
five-row mode uses y=1,8,15,22,29. System RAM uses the same explicit units as VRAM.
Two GPU columns at x=54..104 and x=108..159 are visible together, with dividers
at x=51 and x=105 (the first column's glyphs end before its right divider).
Each GPU has five lines at y=1,8,15,22,29: vendor/index plus PCI bus:slot.function,
UTL percent, U used VRAM, T total VRAM, TEM temperature in Celsius (C).
For example NV1 01:00.0 and AMD2 11:00.0 identify different cards. Indices are
one-based positions in the selected PCI-sorted inventory, not NVIDIA ordinals;
startup diagnostics map each index to its full domain:bus:slot.function address.
More than two GPUs cycle through consecutive pairs every five seconds from
startup (1/2, 3/4, ..., then 1/2); the last odd card occupies the first column.
All cards are collected continuously, not just the visible pair. The display
refresh interval can delay a page change: each draw advances at most one pair,
so slow refresh never skips pairs. Inventory is bounded at 64 cards;
overflow/enumeration failure is an explicit startup error, never silent omission.
Use explicit PCI selection or off to recover. Hotplug requires restart.
The bottom info bar at y=37..42 and all non-summary screens retain their geometry.
Used and total are on separate lines so each retains an explicit MiB/GiB unit
(TiB/PiB for very large values), rounded to one decimal. Other screens do not
change. U/T refer to VRAM, not memory-controller utilization or system RAM;
on AMD APUs the driver may report only the reserved VRAM carveout.
AMD reads gpu_busy_percent, mem_info_vram_used/total (bytes), and the selected
PCI device's hwmon/temp1_input (millidegrees C). NVIDIA optionally executes
/usr/bin/nvidia-smi with pci.bus_id,utilization.gpu,memory.used,memory.total,temperature.gpu
and --format=csv,noheader,nounits: one query for all NVIDIA cards, matched by PCI
identity (including NVIDIA's eight-digit domain form), not response order. No CUDA/NVML SDK
or additional build dependency is needed. Run as an ordinary user.
Independent AMD workers and one batched NVIDIA worker wait two seconds between
queries; drawing uses only a mutex-protected snapshot array. A stalled AMD read
does not block another card or NVIDIA. NVIDIA queries share one 750ms monotonic deadline, fixed
arguments/environment, no shell, bounded output, and SIGKILL on timeout or
parent death. An unreaped child prevents further launches. Samples older than
five seconds display N/A per card, including when a sysfs read stalls. Worker
shutdown has one shared one-second join/reap budget; a stuck kernel read can
outlive that budget in static storage until process exit (no restart after a
failed stop). Normal application termination exits the process; no driver waits
are added to signal handling. The -r display
refresh interval still applies independently. No GPU settings are written.
Official interface references:
https://docs.kernel.org/gpu/amdgpu/thermal.html
https://docs.kernel.org/gpu/amdgpu/driver-misc.html
https://docs.nvidia.com/deploy/nvidia-smi/index.html
GPU validation:
After configuring/building, make check runs compiled parsing, units, missing-data,
expiry, selection, subprocess deadline/reaping/parent-death and descriptor checks,
plus real libg15render buffer checks. No daemon, input devices or synthetic GPU
sysfs fixtures are used. ./gpu-test auto additionally checks every real GPU,
its sysfs vendor identity and background cache (requires all four metrics).
Explicit PCI arguments still test one card. ./render-test auto output writes
real live 160x43 buffers to output-0.pgm and output-1.pgm (five/four rows), plus
output (four rows). ./render-test off output renders the original GPU-off layout.
For in-memory cases, ./render-test off output COUNT PAGE [large|unknown] selects
1..64 cards and a zero-based pair page. Omit PAGE to export the last pair.
The default values exercise LLONG_MAX VRAM and -273C/1000C; large uses
100.0/192.0GiB and 1000C, while unknown uses N/A for all GPU metrics.
For example ./render-test off output 2 tests worst-width dual-card values;
./render-test off output 3 1 tests the odd last page. With no arguments,
make check retains the worst-width two-card case. These are rendering inputs,
not simulated hardware or claims about the installed GPUs.
These do not connect to g15daemon and are not physical LCD tests.
make check also runs tests/render-layouts.sh for off, single, dual, large,
unknown, odd-last and 64-card layouts, each in both summary modes. In addition
to isolated-column comparisons, the test compares final GPU text, vertical
TOTAL and time-footer cells directly with the installed font's bitmap bits.
This independent oracle includes blank cells and checks full glyph bounds;
repeating renderString on another canvas alone cannot detect shared clipping.
Canvas initialization is tested starting from poisoned storage.
Startup regression: make check also runs runtime-test through the full main
loop for three frames with real libgtop and bitmap rendering. Only daemon
transport is replaced by a private socketpair; no keyboard events are sent.
./runtime-test auto 21 additionally requires valid live GPU samples after
twenty seconds. This is not a physical LCD or real daemon integration test.
The bitmap-only client initializes its own canvas, avoiding g15r_initCanvas:
a TTF-enabled renderer can otherwise write beyond a non-TTF caller's canvas
and corrupt malloc metadata at startup, even with --gpu off.
Each visual export also writes output-0.bin and output-1.bin: the actual 860
packed framebuffer bytes (one means black). With ImageMagick installed, run
sh tests/render-images.sh /absolute/output/directory after generating frames
there. It compares packed PBM, PGM and native PNG pixels, generates exact
nearest-neighbor 8x images, and checks the 8x-to-native round trip. Per-scenario
contact sheets use 4x tiles with labels outside the LCD and one column, avoiding
very wide sheets that viewers may downscale. No trim or crop is applied.
Inspect the individual native and 8x files as well as the contact sheets.
The supplied default small font has only three ink columns plus a blank column
per character; its N, M and other shapes can look incomplete when enlarged.
Use the bitmap checks to distinguish those font shapes from missing pixels.
Features: Features:
Summary Screen displays 4 or 5 indicators from CPU/Frequency/Memory/NET/Temperature/Fan Speed/Swap, Summary Screen displays 4 or 5 indicators from CPU/Frequency/Memory/NET/Temperature/Fan Speed/Swap,
along with current time. along with current time.
+41 -11
View File
@@ -3,6 +3,13 @@ G15STATS
A CPU/Memory/Swap/Network/Battery/Temperature/Fan Speed/CPU Frequencies usage meter for G15Daemon. A CPU/Memory/Swap/Network/Battery/Temperature/Fan Speed/CPU Frequencies usage meter for G15Daemon.
=======
Warning
=======
I'm discontinuing this after someone made a fuzz about a feature he decided he want a decade later.
And as far I am concerned, Arch Linux's AUR administrators find this behavior just fine, so I'm not wasting my efforts on this anymore.
I can still fix issues as I always did and help via mail, but keep in mind Arch Linux is impossible to be supported.
======== ========
Features Features
======== ========
@@ -23,6 +30,40 @@ Requirements
- libgtop - libgtop
GPU monitoring
--------------
The summary automatically collects ALL supported PCI addresses (amdgpu or nvidia),
including mixed vendors. ``--gpu dddd:bb:ss.f`` (alias ``-gpu``) selects
a card; ``--gpu off`` disables collection and restores the original layout.
``--gpu auto`` is the default. Intel, nouveau and radeon are not supported.
Missing metrics show N/A. No supported GPU in auto mode leaves the layout intact.
An explicitly selected missing card keeps an N/A column. Selection is at startup.
Each column shows vendor/index and PCI bus:slot.function, UTL %, U used VRAM, T total VRAM, and TEM in C on five
lines. VRAM uses explicit MiB/GiB units (TiB/PiB if needed), with one decimal.
One GPU retains summary labels and shortened graphs. Two GPUs appear together
beside compact CPU/RAM-used/RAM-total/SWP rows (plus GPU count in five-row mode).
RAM also has explicit units. More than two cycle in PCI-sorted pairs every five
seconds from startup; all continue collecting. NV1/AMD2-style indices are one-based
selected-inventory positions, mapped to full PCI addresses in startup diagnostics.
The 64-card bound fails explicitly on overflow. The bottom bar and other screens
are unchanged. AMD APU VRAM may mean its reserved
carveout, not all shared system memory.
AMD uses read-only PCI-device sysfs/hwmon files. NVIDIA uses the optional
``/usr/bin/nvidia-smi`` without a shell or SDK dependency. Independent AMD workers
and one PCI-keyed all-NVIDIA query wait two seconds between samples, with a shared
750ms NVIDIA deadline and per-card five-second cache expiry. Shutdown has one
one-second worker join/reap budget. Display refresh remains controlled by ``-r``. See README for
exact layout, interfaces and subprocess lifecycle details. Run as an ordinary user.
``make check`` covers GPU-off, single-card and multi-card native framebuffers.
Final GPU, vertical TOTAL and time-footer glyphs are checked against font bitmap
bits independently of the text renderer. See README for packed framebuffer
exports and ``tests/render-images.sh``, which generates verified native/nearest-8x
PNGs and narrow contact sheets without cropping the LCD area.
======= =======
Options Options
======= =======
@@ -50,14 +91,3 @@ Once running, the separate screens can be switched to as follows:
| ``L3``: Next Screen | ``L3``: Next Screen
| ``L4``: Alternative Screen (Doesn't work on Swap, Memory and Battery Screen) | ``L4``: Alternative Screen (Doesn't work on Swap, Memory and Battery Screen)
| ``L5``: Bottom Info bar mode | ``L5``: Bottom Info bar mode
=============
How to donate
=============
If you find this repo useful (don't forget to pay a visit to the related
repos too), you can buy me a beer:
:BTC: 3ECzX5UhcFSRv6gBBYLNBc7zGP9UA5Ppmn
:ETH: 0x7E17Ac09Fa7e6F80284a75577B5c1cbaAD566C1c
Vendored
+6 -11
View File
@@ -1,4 +1,4 @@
# generated automatically by aclocal 1.16.2 -*- Autoconf -*- # generated automatically by aclocal 1.16.3 -*- Autoconf -*-
# Copyright (C) 1996-2020 Free Software Foundation, Inc. # Copyright (C) 1996-2020 Free Software Foundation, Inc.
@@ -14,8 +14,8 @@
m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])]) m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])])
m4_ifndef([AC_AUTOCONF_VERSION], m4_ifndef([AC_AUTOCONF_VERSION],
[m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.70],, m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.71],,
[m4_warning([this file was generated for autoconf 2.70. [m4_warning([this file was generated for autoconf 2.71.
You have another version of autoconf. It may work, but is not guaranteed to. You have another version of autoconf. It may work, but is not guaranteed to.
If you have problems, you may need to regenerate the build system entirely. If you have problems, you may need to regenerate the build system entirely.
To do so, use the procedure documented by the package, typically 'autoreconf'.])]) To do so, use the procedure documented by the package, typically 'autoreconf'.])])
@@ -9432,7 +9432,7 @@ AC_DEFUN([AM_AUTOMAKE_VERSION],
[am__api_version='1.16' [am__api_version='1.16'
dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to
dnl require some minimum version. Point them to the right macro. dnl require some minimum version. Point them to the right macro.
m4_if([$1], [1.16.2], [], m4_if([$1], [1.16.3], [],
[AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl
]) ])
@@ -9448,7 +9448,7 @@ m4_define([_AM_AUTOCONF_VERSION], [])
# Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced. # Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced.
# This function is AC_REQUIREd by AM_INIT_AUTOMAKE. # This function is AC_REQUIREd by AM_INIT_AUTOMAKE.
AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION], AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION],
[AM_AUTOMAKE_VERSION([1.16.2])dnl [AM_AUTOMAKE_VERSION([1.16.3])dnl
m4_ifndef([AC_AUTOCONF_VERSION], m4_ifndef([AC_AUTOCONF_VERSION],
[m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
_AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))]) _AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))])
@@ -10100,12 +10100,7 @@ AC_DEFUN([AM_MISSING_HAS_RUN],
[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl [AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
AC_REQUIRE_AUX_FILE([missing])dnl AC_REQUIRE_AUX_FILE([missing])dnl
if test x"${MISSING+set}" != xset; then if test x"${MISSING+set}" != xset; then
case $am_aux_dir in MISSING="\${SHELL} '$am_aux_dir/missing'"
*\ * | *\ *)
MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;;
*)
MISSING="\${SHELL} $am_aux_dir/missing" ;;
esac
fi fi
# Use eval to expand $SHELL # Use eval to expand $SHELL
if eval "$MISSING --is-lightweight"; then if eval "$MISSING --is-lightweight"; then
-8
View File
@@ -1,8 +0,0 @@
#! /bin/sh
[ -d config ] || mkdir config
set -x
aclocal -I config
libtoolize --force --copy
autoheader
automake --add-missing --copy
autoconf
+17 -10
View File
@@ -1,8 +1,8 @@
#! /bin/sh #! /bin/sh
# Attempt to guess a canonical system name. # Attempt to guess a canonical system name.
# Copyright 1992-2020 Free Software Foundation, Inc. # Copyright 1992-2021 Free Software Foundation, Inc.
timestamp='2020-11-19' timestamp='2021-04-21'
# This file is free software; you can redistribute it and/or modify it # This file is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by # under the terms of the GNU General Public License as published by
@@ -50,7 +50,7 @@ version="\
GNU config.guess ($timestamp) GNU config.guess ($timestamp)
Originally written by Per Bothner. Originally written by Per Bothner.
Copyright 1992-2020 Free Software Foundation, Inc. Copyright 1992-2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
@@ -188,10 +188,9 @@ case "$UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION" in
# #
# Note: NetBSD doesn't particularly care about the vendor # Note: NetBSD doesn't particularly care about the vendor
# portion of the name. We always set it to "unknown". # portion of the name. We always set it to "unknown".
sysctl="sysctl -n hw.machine_arch"
UNAME_MACHINE_ARCH=$( (uname -p 2>/dev/null || \ UNAME_MACHINE_ARCH=$( (uname -p 2>/dev/null || \
"/sbin/$sysctl" 2>/dev/null || \ /sbin/sysctl -n hw.machine_arch 2>/dev/null || \
"/usr/sbin/$sysctl" 2>/dev/null || \ /usr/sbin/sysctl -n hw.machine_arch 2>/dev/null || \
echo unknown)) echo unknown))
case "$UNAME_MACHINE_ARCH" in case "$UNAME_MACHINE_ARCH" in
aarch64eb) machine=aarch64_be-unknown ;; aarch64eb) machine=aarch64_be-unknown ;;
@@ -262,6 +261,10 @@ case "$UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION" in
UNAME_MACHINE_ARCH=$(arch | sed 's/OpenBSD.//') UNAME_MACHINE_ARCH=$(arch | sed 's/OpenBSD.//')
echo "$UNAME_MACHINE_ARCH"-unknown-openbsd"$UNAME_RELEASE" echo "$UNAME_MACHINE_ARCH"-unknown-openbsd"$UNAME_RELEASE"
exit ;; exit ;;
*:SecBSD:*:*)
UNAME_MACHINE_ARCH=$(arch | sed 's/SecBSD.//')
echo "$UNAME_MACHINE_ARCH"-unknown-secbsd"$UNAME_RELEASE"
exit ;;
*:LibertyBSD:*:*) *:LibertyBSD:*:*)
UNAME_MACHINE_ARCH=$(arch | sed 's/^.*BSD\.//') UNAME_MACHINE_ARCH=$(arch | sed 's/^.*BSD\.//')
echo "$UNAME_MACHINE_ARCH"-unknown-libertybsd"$UNAME_RELEASE" echo "$UNAME_MACHINE_ARCH"-unknown-libertybsd"$UNAME_RELEASE"
@@ -950,7 +953,7 @@ EOF
if test "$?" = 0 ; then LIBC=gnulibc1 ; fi if test "$?" = 0 ; then LIBC=gnulibc1 ; fi
echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
exit ;; exit ;;
arc:Linux:*:* | arceb:Linux:*:*) arc:Linux:*:* | arceb:Linux:*:* | arc64:Linux:*:*)
echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
exit ;; exit ;;
arm*:Linux:*:*) arm*:Linux:*:*)
@@ -996,6 +999,9 @@ EOF
k1om:Linux:*:*) k1om:Linux:*:*)
echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
exit ;; exit ;;
loongarch32:Linux:*:* | loongarch64:Linux:*:* | loongarchx32:Linux:*:*)
echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
exit ;;
m32r*:Linux:*:*) m32r*:Linux:*:*)
echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
exit ;; exit ;;
@@ -1084,7 +1090,7 @@ EOF
ppcle:Linux:*:*) ppcle:Linux:*:*)
echo powerpcle-unknown-linux-"$LIBC" echo powerpcle-unknown-linux-"$LIBC"
exit ;; exit ;;
riscv32:Linux:*:* | riscv64:Linux:*:*) riscv32:Linux:*:* | riscv32be:Linux:*:* | riscv64:Linux:*:* | riscv64be:Linux:*:*)
echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
exit ;; exit ;;
s390:Linux:*:* | s390x:Linux:*:*) s390:Linux:*:* | s390x:Linux:*:*)
@@ -1480,8 +1486,8 @@ EOF
i*86:rdos:*:*) i*86:rdos:*:*)
echo "$UNAME_MACHINE"-pc-rdos echo "$UNAME_MACHINE"-pc-rdos
exit ;; exit ;;
i*86:AROS:*:*) *:AROS:*:*)
echo "$UNAME_MACHINE"-pc-aros echo "$UNAME_MACHINE"-unknown-aros
exit ;; exit ;;
x86_64:VMkernel:*:*) x86_64:VMkernel:*:*)
echo "$UNAME_MACHINE"-unknown-esx echo "$UNAME_MACHINE"-unknown-esx
@@ -1696,3 +1702,4 @@ exit 1
# time-stamp-format: "%:y-%02m-%02d" # time-stamp-format: "%:y-%02m-%02d"
# time-stamp-end: "'" # time-stamp-end: "'"
# End: # End:
+21 -10
View File
@@ -1,8 +1,8 @@
#! /bin/sh #! /bin/sh
# Configuration validation subroutine script. # Configuration validation subroutine script.
# Copyright 1992-2020 Free Software Foundation, Inc. # Copyright 1992-2021 Free Software Foundation, Inc.
timestamp='2020-12-02' timestamp='2021-04-30'
# This file is free software; you can redistribute it and/or modify it # This file is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by # under the terms of the GNU General Public License as published by
@@ -67,7 +67,7 @@ Report bugs and patches to <config-patches@gnu.org>."
version="\ version="\
GNU config.sub ($timestamp) GNU config.sub ($timestamp)
Copyright 1992-2020 Free Software Foundation, Inc. Copyright 1992-2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
@@ -1165,7 +1165,7 @@ case $cpu-$vendor in
| alphapca5[67] | alpha64pca5[67] \ | alphapca5[67] | alpha64pca5[67] \
| am33_2.0 \ | am33_2.0 \
| amdgcn \ | amdgcn \
| arc | arceb \ | arc | arceb | arc64 \
| arm | arm[lb]e | arme[lb] | armv* \ | arm | arm[lb]e | arme[lb] | armv* \
| avr | avr32 \ | avr | avr32 \
| asmjs \ | asmjs \
@@ -1185,6 +1185,7 @@ case $cpu-$vendor in
| k1om \ | k1om \
| le32 | le64 \ | le32 | le64 \
| lm32 \ | lm32 \
| loongarch32 | loongarch64 | loongarchx32 \
| m32c | m32r | m32rle \ | m32c | m32r | m32rle \
| m5200 | m68000 | m680[012346]0 | m68360 | m683?2 | m68k \ | m5200 | m68000 | m680[012346]0 | m68360 | m683?2 | m68k \
| m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x \ | m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x \
@@ -1203,9 +1204,13 @@ case $cpu-$vendor in
| mips64vr5900 | mips64vr5900el \ | mips64vr5900 | mips64vr5900el \
| mipsisa32 | mipsisa32el \ | mipsisa32 | mipsisa32el \
| mipsisa32r2 | mipsisa32r2el \ | mipsisa32r2 | mipsisa32r2el \
| mipsisa32r3 | mipsisa32r3el \
| mipsisa32r5 | mipsisa32r5el \
| mipsisa32r6 | mipsisa32r6el \ | mipsisa32r6 | mipsisa32r6el \
| mipsisa64 | mipsisa64el \ | mipsisa64 | mipsisa64el \
| mipsisa64r2 | mipsisa64r2el \ | mipsisa64r2 | mipsisa64r2el \
| mipsisa64r3 | mipsisa64r3el \
| mipsisa64r5 | mipsisa64r5el \
| mipsisa64r6 | mipsisa64r6el \ | mipsisa64r6 | mipsisa64r6el \
| mipsisa64sb1 | mipsisa64sb1el \ | mipsisa64sb1 | mipsisa64sb1el \
| mipsisa64sr71k | mipsisa64sr71kel \ | mipsisa64sr71k | mipsisa64sr71kel \
@@ -1229,7 +1234,7 @@ case $cpu-$vendor in
| powerpc | powerpc64 | powerpc64le | powerpcle | powerpcspe \ | powerpc | powerpc64 | powerpc64le | powerpcle | powerpcspe \
| pru \ | pru \
| pyramid \ | pyramid \
| riscv | riscv32 | riscv64 \ | riscv | riscv32 | riscv32be | riscv64 | riscv64be \
| rl78 | romp | rs6000 | rx \ | rl78 | romp | rs6000 | rx \
| s390 | s390x \ | s390 | s390x \
| score \ | score \
@@ -1682,11 +1687,14 @@ fi
# Now, validate our (potentially fixed-up) OS. # Now, validate our (potentially fixed-up) OS.
case $os in case $os in
# Sometimes we do "kernel-abi", so those need to count as OSes. # Sometimes we do "kernel-libc", so those need to count as OSes.
musl* | newlib* | uclibc*) musl* | newlib* | uclibc*)
;; ;;
# Likewise for "kernel-libc" # Likewise for "kernel-abi"
eabi | eabihf | gnueabi | gnueabihf) eabi* | gnueabi*)
;;
# VxWorks passes extra cpu info in the 4th filed.
simlinux | simwindows | spe)
;; ;;
# Now accept the basic system types. # Now accept the basic system types.
# The portable systems comes first. # The portable systems comes first.
@@ -1703,12 +1711,12 @@ case $os in
| nindy* | vxsim* | vxworks* | ebmon* | hms* | mvs* \ | nindy* | vxsim* | vxworks* | ebmon* | hms* | mvs* \
| clix* | riscos* | uniplus* | iris* | isc* | rtu* | xenix* \ | clix* | riscos* | uniplus* | iris* | isc* | rtu* | xenix* \
| mirbsd* | netbsd* | dicos* | openedition* | ose* \ | mirbsd* | netbsd* | dicos* | openedition* | ose* \
| bitrig* | openbsd* | solidbsd* | libertybsd* | os108* \ | bitrig* | openbsd* | secbsd* | solidbsd* | libertybsd* | os108* \
| ekkobsd* | freebsd* | riscix* | lynxos* | os400* \ | ekkobsd* | freebsd* | riscix* | lynxos* | os400* \
| bosx* | nextstep* | cxux* | aout* | elf* | oabi* \ | bosx* | nextstep* | cxux* | aout* | elf* | oabi* \
| ptx* | coff* | ecoff* | winnt* | domain* | vsta* \ | ptx* | coff* | ecoff* | winnt* | domain* | vsta* \
| udi* | lites* | ieee* | go32* | aux* | hcos* \ | udi* | lites* | ieee* | go32* | aux* | hcos* \
| chorusrdb* | cegcc* | glidix* \ | chorusrdb* | cegcc* | glidix* | serenity* \
| cygwin* | msys* | pe* | moss* | proelf* | rtems* \ | cygwin* | msys* | pe* | moss* | proelf* | rtems* \
| midipix* | mingw32* | mingw64* | mint* \ | midipix* | mingw32* | mingw64* | mint* \
| uxpv* | beos* | mpeix* | udk* | moxiebox* \ | uxpv* | beos* | mpeix* | udk* | moxiebox* \
@@ -1750,6 +1758,8 @@ case $kernel-$os in
;; ;;
kfreebsd*-gnu* | kopensolaris*-gnu*) kfreebsd*-gnu* | kopensolaris*-gnu*)
;; ;;
vxworks-simlinux | vxworks-simwindows | vxworks-spe)
;;
nto-qnx*) nto-qnx*)
;; ;;
os2-emx) os2-emx)
@@ -1852,3 +1862,4 @@ exit
# time-stamp-format: "%:y-%02m-%02d" # time-stamp-format: "%:y-%02m-%02d"
# time-stamp-end: "'" # time-stamp-end: "'"
# End: # End:
Regular → Executable
View File
File mode changed.
Vendored
+83 -96
View File
@@ -1,11 +1,12 @@
#! /bin/sh #! /bin/sh
# Guess values for system-dependent variables and create Makefiles. # Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.70 for g15stats 1.9.8. # Generated by GNU Autoconf 2.71 for g15stats 3.0.
# #
# Report bugs to <menelkir@itroll.org>. # Report bugs to <dmenelkir@gmail.com>.
# #
# #
# Copyright (C) 1992-1996, 1998-2017, 2020 Free Software Foundation, Inc. # Copyright (C) 1992-1996, 1998-2017, 2020-2021 Free Software Foundation,
# Inc.
# #
# #
# This configure script is free software; the Free Software Foundation # This configure script is free software; the Free Software Foundation
@@ -275,7 +276,7 @@ then :
printf "%s\n" "$0: be upgraded to zsh 4.3.4 or later." printf "%s\n" "$0: be upgraded to zsh 4.3.4 or later."
else else
printf "%s\n" "$0: Please tell bug-autoconf@gnu.org and printf "%s\n" "$0: Please tell bug-autoconf@gnu.org and
$0: menelkir@itroll.org about your system, including any $0: dmenelkir@gmail.com about your system, including any
$0: error possibly output before this message. Then install $0: error possibly output before this message. Then install
$0: a modern shell, or manually run the script under such a $0: a modern shell, or manually run the script under such a
$0: shell if you do have one." $0: shell if you do have one."
@@ -620,9 +621,9 @@ MAKEFLAGS=
# Identity of this package. # Identity of this package.
PACKAGE_NAME='g15stats' PACKAGE_NAME='g15stats'
PACKAGE_TARNAME='g15stats' PACKAGE_TARNAME='g15stats'
PACKAGE_VERSION='1.9.8' PACKAGE_VERSION='3.0'
PACKAGE_STRING='g15stats 1.9.8' PACKAGE_STRING='g15stats 3.0'
PACKAGE_BUGREPORT='menelkir@itroll.org' PACKAGE_BUGREPORT='dmenelkir@gmail.com'
PACKAGE_URL='' PACKAGE_URL=''
ac_default_prefix=/usr ac_default_prefix=/usr
@@ -1358,7 +1359,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing. # Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh. # This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF cat <<_ACEOF
\`configure' configures g15stats 1.9.8 to adapt to many kinds of systems. \`configure' configures g15stats 3.0 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]... Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1429,7 +1430,7 @@ fi
if test -n "$ac_init_help"; then if test -n "$ac_init_help"; then
case $ac_init_help in case $ac_init_help in
short | recursive ) echo "Configuration of g15stats 1.9.8:";; short | recursive ) echo "Configuration of g15stats 3.0:";;
esac esac
cat <<\_ACEOF cat <<\_ACEOF
@@ -1484,7 +1485,7 @@ Some influential environment variables:
Use these variables to override the choices made by `configure' or to help Use these variables to override the choices made by `configure' or to help
it to find libraries and programs with nonstandard names/locations. it to find libraries and programs with nonstandard names/locations.
Report bugs to <menelkir@itroll.org>. Report bugs to <dmenelkir@gmail.com>.
_ACEOF _ACEOF
ac_status=$? ac_status=$?
fi fi
@@ -1548,10 +1549,10 @@ fi
test -n "$ac_init_help" && exit $ac_status test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then if $ac_init_version; then
cat <<\_ACEOF cat <<\_ACEOF
g15stats configure 1.9.8 g15stats configure 3.0
generated by GNU Autoconf 2.70 generated by GNU Autoconf 2.71
Copyright (C) 2020 Free Software Foundation, Inc. Copyright (C) 2021 Free Software Foundation, Inc.
This configure script is free software; the Free Software Foundation This configure script is free software; the Free Software Foundation
gives unlimited permission to copy, distribute and modify it. gives unlimited permission to copy, distribute and modify it.
_ACEOF _ACEOF
@@ -1766,8 +1767,8 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake. running configure, to aid debugging if configure makes a mistake.
It was created by g15stats $as_me 1.9.8, which was It was created by g15stats $as_me 3.0, which was
generated by GNU Autoconf 2.70. Invocation command line was generated by GNU Autoconf 2.71. Invocation command line was
$ $0$ac_configure_args_raw $ $0$ac_configure_args_raw
@@ -2523,7 +2524,7 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
PACKAGE=g15stats PACKAGE=g15stats
VERSION=1.9.8 VERSION=3.0
am__api_version='1.16' am__api_version='1.16'
@@ -2711,12 +2712,7 @@ am_aux_dir=`cd "$ac_aux_dir" && pwd`
if test x"${MISSING+set}" != xset; then if test x"${MISSING+set}" != xset; then
case $am_aux_dir in MISSING="\${SHELL} '$am_aux_dir/missing'"
*\ * | *\ *)
MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;;
*)
MISSING="\${SHELL} $am_aux_dir/missing" ;;
esac
fi fi
# Use eval to expand $SHELL # Use eval to expand $SHELL
if eval "$MISSING --is-lightweight"; then if eval "$MISSING --is-lightweight"; then
@@ -3987,7 +3983,10 @@ else
CFLAGS= CFLAGS=
fi fi
fi fi
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C11 features" >&5 ac_prog_cc_stdc=no
if test x$ac_prog_cc_stdc = xno
then :
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C11 features" >&5
printf %s "checking for $CC option to enable C11 features... " >&6; } printf %s "checking for $CC option to enable C11 features... " >&6; }
if test ${ac_cv_prog_cc_c11+y} if test ${ac_cv_prog_cc_c11+y}
then : then :
@@ -4011,28 +4010,28 @@ rm -f core conftest.err conftest.$ac_objext conftest.beam
done done
rm -f conftest.$ac_ext rm -f conftest.$ac_ext
CC=$ac_save_CC CC=$ac_save_CC
fi fi
# AC_CACHE_VAL
ac_prog_cc_stdc_options= if test "x$ac_cv_prog_cc_c11" = xno
case "x$ac_cv_prog_cc_c11" in #(
x) :
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
printf "%s\n" "none needed" >&6; } ;; #(
xno) :
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
printf "%s\n" "unsupported" >&6; } ;; #(
*) :
ac_prog_cc_stdc_options=" $ac_cv_prog_cc_c11"
CC="$CC$ac_prog_cc_stdc_options"
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c11" >&5
printf "%s\n" "$ac_cv_prog_cc_c11" >&6; } ;;
esac
if test "x$ac_cv_prog_cc_c11" != xno
then : then :
ac_prog_cc_stdc=c11 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c11 printf "%s\n" "unsupported" >&6; }
else $as_nop else $as_nop
if test "x$ac_cv_prog_cc_c11" = x
then :
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
printf "%s\n" "none needed" >&6; }
else $as_nop
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c11" >&5
printf "%s\n" "$ac_cv_prog_cc_c11" >&6; }
CC="$CC $ac_cv_prog_cc_c11"
fi
ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c11
ac_prog_cc_stdc=c11
fi
fi
if test x$ac_prog_cc_stdc = xno
then :
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C99 features" >&5 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C99 features" >&5
printf %s "checking for $CC option to enable C99 features... " >&6; } printf %s "checking for $CC option to enable C99 features... " >&6; }
if test ${ac_cv_prog_cc_c99+y} if test ${ac_cv_prog_cc_c99+y}
@@ -4043,9 +4042,9 @@ else $as_nop
ac_save_CC=$CC ac_save_CC=$CC
cat confdefs.h - <<_ACEOF >conftest.$ac_ext cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */ /* end confdefs.h. */
$ac_c_conftest_c89_program $ac_c_conftest_c99_program
_ACEOF _ACEOF
for ac_arg in '' -std=gnu99 -std=c99 -c99 -AC99 -D_STDC_C99= -qlanglvl=extc1x -qlanglvl=extc99 for ac_arg in '' -std=gnu99 -std=c99 -c99 -qlanglvl=extc1x -qlanglvl=extc99 -AC99 -D_STDC_C99=
do do
CC="$ac_save_CC $ac_arg" CC="$ac_save_CC $ac_arg"
if ac_fn_c_try_compile "$LINENO" if ac_fn_c_try_compile "$LINENO"
@@ -4057,28 +4056,28 @@ rm -f core conftest.err conftest.$ac_objext conftest.beam
done done
rm -f conftest.$ac_ext rm -f conftest.$ac_ext
CC=$ac_save_CC CC=$ac_save_CC
fi fi
# AC_CACHE_VAL
ac_prog_cc_stdc_options= if test "x$ac_cv_prog_cc_c99" = xno
case "x$ac_cv_prog_cc_c99" in #(
x) :
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
printf "%s\n" "none needed" >&6; } ;; #(
xno) :
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
printf "%s\n" "unsupported" >&6; } ;; #(
*) :
ac_prog_cc_stdc_options=" $ac_cv_prog_cc_c99"
CC="$CC$ac_prog_cc_stdc_options"
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5
printf "%s\n" "$ac_cv_prog_cc_c99" >&6; } ;;
esac
if test "x$ac_cv_prog_cc_c99" != xno
then : then :
ac_prog_cc_stdc=c99 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c99 printf "%s\n" "unsupported" >&6; }
else $as_nop else $as_nop
if test "x$ac_cv_prog_cc_c99" = x
then :
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
printf "%s\n" "none needed" >&6; }
else $as_nop
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5
printf "%s\n" "$ac_cv_prog_cc_c99" >&6; }
CC="$CC $ac_cv_prog_cc_c99"
fi
ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c99
ac_prog_cc_stdc=c99
fi
fi
if test x$ac_prog_cc_stdc = xno
then :
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C89 features" >&5 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C89 features" >&5
printf %s "checking for $CC option to enable C89 features... " >&6; } printf %s "checking for $CC option to enable C89 features... " >&6; }
if test ${ac_cv_prog_cc_c89+y} if test ${ac_cv_prog_cc_c89+y}
@@ -4091,8 +4090,7 @@ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */ /* end confdefs.h. */
$ac_c_conftest_c89_program $ac_c_conftest_c89_program
_ACEOF _ACEOF
for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__"
-Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__"
do do
CC="$ac_save_CC $ac_arg" CC="$ac_save_CC $ac_arg"
if ac_fn_c_try_compile "$LINENO" if ac_fn_c_try_compile "$LINENO"
@@ -4104,34 +4102,25 @@ rm -f core conftest.err conftest.$ac_objext conftest.beam
done done
rm -f conftest.$ac_ext rm -f conftest.$ac_ext
CC=$ac_save_CC CC=$ac_save_CC
fi fi
# AC_CACHE_VAL
ac_prog_cc_stdc_options= if test "x$ac_cv_prog_cc_c89" = xno
case "x$ac_cv_prog_cc_c89" in #(
x) :
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
printf "%s\n" "none needed" >&6; } ;; #(
xno) :
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
printf "%s\n" "unsupported" >&6; } ;; #(
*) :
ac_prog_cc_stdc_options=" $ac_cv_prog_cc_c89"
CC="$CC$ac_prog_cc_stdc_options"
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5
printf "%s\n" "$ac_cv_prog_cc_c89" >&6; } ;;
esac
if test "x$ac_cv_prog_cc_c89" != xno
then : then :
ac_prog_cc_stdc=c89 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c89 printf "%s\n" "unsupported" >&6; }
else $as_nop else $as_nop
ac_prog_cc_stdc=no if test "x$ac_cv_prog_cc_c89" = x
ac_cv_prog_cc_stdc=no then :
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
printf "%s\n" "none needed" >&6; }
else $as_nop
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5
printf "%s\n" "$ac_cv_prog_cc_c89" >&6; }
CC="$CC $ac_cv_prog_cc_c89"
fi fi
ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c89
ac_prog_cc_stdc=c89
fi fi
fi fi
ac_ext=c ac_ext=c
@@ -12600,8 +12589,6 @@ else $as_nop
as_fn_error $? "\"libg15daemon_client (or its devel package) not found. please install it\"" "$LINENO" 5 as_fn_error $? "\"libg15daemon_client (or its devel package) not found. please install it\"" "$LINENO" 5
fi fi
# Autoupdate added the next two lines to ensure that your configure
# script's behavior did not change. They are probably safe to remove.
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5
printf %s "checking for egrep... " >&6; } printf %s "checking for egrep... " >&6; }
@@ -13510,8 +13497,8 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their # report actual input values of CONFIG_FILES etc. instead of their
# values after options handling. # values after options handling.
ac_log=" ac_log="
This file was extended by g15stats $as_me 1.9.8, which was This file was extended by g15stats $as_me 3.0, which was
generated by GNU Autoconf 2.70. Invocation command line was generated by GNU Autoconf 2.71. Invocation command line was
CONFIG_FILES = $CONFIG_FILES CONFIG_FILES = $CONFIG_FILES
CONFIG_HEADERS = $CONFIG_HEADERS CONFIG_HEADERS = $CONFIG_HEADERS
@@ -13570,7 +13557,7 @@ $config_headers
Configuration commands: Configuration commands:
$config_commands $config_commands
Report bugs to <menelkir@itroll.org>." Report bugs to <dmenelkir@gmail.com>."
_ACEOF _ACEOF
ac_cs_config=`printf "%s\n" "$ac_configure_args" | sed "$ac_safe_unquote"` ac_cs_config=`printf "%s\n" "$ac_configure_args" | sed "$ac_safe_unquote"`
@@ -13578,11 +13565,11 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config='$ac_cs_config_escaped' ac_cs_config='$ac_cs_config_escaped'
ac_cs_version="\\ ac_cs_version="\\
g15stats config.status 1.9.8 g15stats config.status 3.0
configured by $0, generated by GNU Autoconf 2.70, configured by $0, generated by GNU Autoconf 2.71,
with options \\"\$ac_cs_config\\" with options \\"\$ac_cs_config\\"
Copyright (C) 2020 Free Software Foundation, Inc. Copyright (C) 2021 Free Software Foundation, Inc.
This config.status script is free software; the Free Software Foundation This config.status script is free software; the Free Software Foundation
gives unlimited permission to copy, distribute and modify it." gives unlimited permission to copy, distribute and modify it."
+11 -8
View File
@@ -1,28 +1,31 @@
AC_INIT(g15stats,[1.9.8], [menelkir@itroll.org]) AC_INIT([g15stats],[3.0],[dmenelkir@gmail.com])
PACKAGE=g15stats PACKAGE=g15stats
VERSION=1.9.8 VERSION=3.0.1
AC_PREFIX_DEFAULT(/usr) AC_PREFIX_DEFAULT(/usr)
AC_CONFIG_AUX_DIR(config) AC_CONFIG_AUX_DIR(config)
AM_INIT_AUTOMAKE($PACKAGE, $VERSION) AM_INIT_AUTOMAKE($PACKAGE, $VERSION)
AC_CONFIG_HEADER([config.h]) AC_CONFIG_HEADERS([config.h])
AC_PROG_CC AC_PROG_CC
AC_PROG_LIBTOOL LT_INIT
AC_PROG_RANLIB AC_PROG_RANLIB
AC_CHECK_LIB([g15daemon_client], [g15daemon_version],,AC_MSG_ERROR(["libg15daemon_client (or its devel package) not found. please install it"])) AC_CHECK_LIB([g15daemon_client], [g15daemon_version],,AC_MSG_ERROR(["libg15daemon_client (or its devel package) not found. please install it"]))
AC_HEADER_STDC AC_CHECK_INCLUDES_DEFAULT
AC_PROG_EGREP
AC_HEADER_SYS_WAIT AC_HEADER_SYS_WAIT
AC_CHECK_HEADERS(g15daemon_client.h,,AC_MSG_ERROR(["libg15daemon_client (or its devel package) could not be found. please install it"])) AC_CHECK_HEADERS(g15daemon_client.h,,AC_MSG_ERROR(["libg15daemon_client (or its devel package) could not be found. please install it"]))
AC_CHECK_HEADERS(libg15render.h,,AC_MSG_ERROR(["libg15render (or its devel package) could not be found. please install it"])) AC_CHECK_HEADERS(libg15render.h,,AC_MSG_ERROR(["libg15render (or its devel package) could not be found. please install it"]))
PKG_CHECK_MODULES(GTOP,libgtop-2.0,,) PKG_CHECK_MODULES(GTOP,libgtop-2.0,,)
AC_ARG_WITH(gtop-includes,AC_HELP_STRING([--with-gtop-includes],[Path to libgtop includes (Autodetect)]),GTOP_CFLAGS="-I$withval",) AC_ARG_WITH(gtop-includes,AS_HELP_STRING([--with-gtop-includes],[Path to libgtop includes (Autodetect)]),GTOP_CFLAGS="-I$withval",)
AC_ARG_WITH(gtop-libs,AC_HELP_STRING([--with-gtop-libs],[Path to libgtop libraries (Autodetect)]),GTOP_LIBS="-L$withval -lgtop-2.0 -lglib-2.0",) AC_ARG_WITH(gtop-libs,AS_HELP_STRING([--with-gtop-libs],[Path to libgtop libraries (Autodetect)]),GTOP_LIBS="-L$withval -lgtop-2.0 -lglib-2.0",)
CPPFLAGS="$CPPFLAGS $GTOP_CFLAGS" CPPFLAGS="$CPPFLAGS $GTOP_CFLAGS"
CFLAGS="$CPPFLAGS $GTOP_CFLAGS" CFLAGS="$CPPFLAGS $GTOP_CFLAGS"
LDFLAGS="$LDFLAGS $GTOP_LIBS" LDFLAGS="$LDFLAGS $GTOP_LIBS"
AC_OUTPUT(Makefile) AC_CONFIG_FILES([Makefile])
AC_OUTPUT
+14
View File
@@ -0,0 +1,14 @@
[main]
@type = classic, oneshot
@name = g15stats
@description = "g15stats"
@depends = ()
@options = ( log env )
[start]
@build = auto,custom
@execute = ( g15stats -d )
[stop]
@build = auto,custom
@execute = ( g15stats -k )
Regular → Executable
+3 -3
View File
@@ -5,9 +5,9 @@
IFACE="eth0" IFACE="eth0"
# Please see g15stats --help for meanings # Please see g15stats --help for meanings
#TEMP_INPUT=1 TEMP_INPUT=1
#GLOBAL_TEMP_INPUT=1 GLOBAL_TEMP_INPUT=1
#FAN_INPUT=1 FAN_INPUT=1
# please see g15stats --help for all options # please see g15stats --help for all options
# As of 1.9.7, the options where: # As of 1.9.7, the options where:
+26
View File
@@ -0,0 +1,26 @@
#!/bin/sh
# $FreeBSD$
#
# PROVIDE: g15stats
# REQUIRE: LOGIN g15daemon
# KEYWORD: shutdown
#
# Add these lines to /etc/rc.conf.local or /etc/rc.conf
# to enable this service:
#
# g15stats_enable (bool): Set to NO by default.
# Set it to YES to enable doormand.
. /etc/rc.subr
name=g15stats
rcvar="${name}_enable"
load_rc_config $name
: ${g15stats_enable:="NO"}
command=%%PREFIX%%/bin/${name}
command_args='-d -i re0'
run_rc_command "$1"
-1
View File
@@ -6,7 +6,6 @@ PIDFILE=/var/run/${SVCNAME}.pid
depend() { depend() {
need g15daemon need g15daemon
after xdm
} }
start() { start() {
-2
View File
@@ -1,4 +1,2 @@
#!/bin/sh #!/bin/sh
exec 2>&1
sv check g15daemon >/dev/null || exit 1
exec g15stats exec g15stats
Regular → Executable
View File
File mode changed.
+25
View File
@@ -0,0 +1,25 @@
#!/bin/sh
. /etc/rc.subr
case $1 in
start)
msg "Starting g15stats..."
start_daemon /usr/bin/g15stats -d
;;
stop)
msg "Stopping g15stats..."
stop_daemon /usr/bin/g15stats
;;
restart)
$0 stop
sleep 1
$0 start
;;
status)
status_daemon /usr/bin/g15stats
;;
*)
echo "usage: $0 [start|stop|restart|status]"
;;
esac
+53
View File
@@ -0,0 +1,53 @@
Debian packaging import and fork provenance
==========================================
Imported on 2026-09-07 from the official Debian archive:
https://archive.debian.org/debian/pool/main/g/g15stats/
SHA-256 (filenames relative to that URL):
79289e9f182e086dc5bfe0b1b594fedc36ad7610ee1daec19b05d44c4494d3fa g15stats_1.9.2-2.dsc
ce724380069ee62563f2898b1bbd0afb80bb632cb6dbf3d1747871f7cdfefbb3 g15stats_1.9.2-2.diff.gz
9dda49a3f09e66ce738ceecb07f2c0fef7ecd5ecbd06e435b340dfca35a37da4 g15stats_1.9.2.orig.tar.gz
The diff (2316 bytes) and orig tarball (25190 bytes) match the SHA-256
checksums in the downloaded .dsc. The .dsc's own checksum was computed
locally. The byte-exact .dsc and diff are retained in debian/provenance/;
the diff preserves ALL original debian files, the manual and linker fix.
The tarball can be downloaded from the URL above and verified independently.
Checksum matching alone does not authenticate the uploader's PGP signature.
Verification was attempted with Debian 13's debian-keyring,
debian-maintainers and debian-nonupload keyrings: gpgv reported
"No public key" for DSA key F9935424B1DF9A57 (signature dated 2011-03-15).
No successful signature verification is claimed here.
Archive package: g15stats 1.9.2-2, source format 1.0, maintained by
Giacomo Catenazzi <cate@debian.org>. Original Debian packaging copyright:
2008 Giacomo A. Catenazzi, GPL-2-or-later. All three historical changelog
entries remain unchanged. The archive adds g15stats.1 outside debian/;
it is now debian/g15stats.1, extended for the fork, retaining Mike Lampard's
author credit. The archive's only upstream source patch adds -lpthread -lm
to Makefile.am, credited to Bhavani Shankar in the changelog (#554382).
Both libraries already appear in the fork: do not apply that patch twice.
All nine archived debian files were imported. control, rules, docs, install,
manpages, watch and copyright were adapted; changelog was prepended;
compat was raised from 5 to 13, with Build-Depends: debhelper (>= 13~).
Obsolete CDBS cleanup and SourceForge watching were replaced, not omitted
without a record. No archived quilt series existed.
Upstream snapshot (unmodified tracked files in the orig archive):
Menelkir master 95de11a526346b3d9fd33df051b330c36b1c8a16
Commit date: 2024-06-09; subject: Update README.rst
Upstream: https://gitlab.com/menelkir/g15stats
Local fork origin: https://gt.uo1.net/mikhail/g15stats
Version rationale: git describe gives 3.0.3-13-g95de11a and the tracked
Arch PKGBUILD declares 3.0.3. configure.ac instead has stale AC_INIT=3.0
and VERSION=3.0.1, including at tag 3.0.3. Therefore this package is
3.0.3+git20240609.95de11a-1~local1, NOT Debian's old 1.9.2 nor a new
upstream release. The quilt patch aligns the embedded version and modernizes
only the build system. Runtime engine files remain untouched.
The new changelog/patch identity is the configured local Git user,
Mikhail Yevchenko <m.ṥῥẚɱ.ѓѐḿởύḙ@uo1.net>; it does not impersonate
the historical Debian maintainer or claim upstream authorship.
+52
View File
@@ -0,0 +1,52 @@
g15stats for Debian 13 (local fork package)
=========================================
This packaging targets Linux and debhelper compatibility 13. See PROVENANCE
for the complete Debian 1.9.2-2 import, licensing and snapshot version choice.
All upstream modifications are in debian/patches; do not build in the checkout
when you want it to remain pristine.
Clean source preparation (outside the checkout)
----------------------------------------------
Export ONLY tracked upstream files at commit
95de11a526346b3d9fd33df051b330c36b1c8a16 using git archive, with directory
prefix g15stats-3.0.3+git20240609.95de11a/. Compress with xz to produce
g15stats_3.0.3+git20240609.95de11a.orig.tar.xz. Extract in a temporary build
directory, then copy this debian/ directory into it. Never tar the working
checkout: .git, quilt state and untracked build outputs must not be included.
In a disposable Debian 13 container, install build-essential, debhelper,
autoconf, automake, libtool, pkgconf, libgtop2-dev, libg15-dev,
libg15render-dev and libg15daemon-client-dev (apt-get build-dep on the staged
source directory also resolves Build-Depends). Install lintian for QA.
If the already-built local G15 libraries are available, install their runtime
and development .deb files instead of the distro G15 libraries. Keep libgtop
and other dependencies from Debian 13. Do not install packages on the host.
Run dpkg-buildpackage -S -us -uc in the staged source directory. In another
empty directory/container, extract the resulting .dsc with dpkg-source -x,
then run dpkg-buildpackage -b -us -uc. Source packages here are unsigned;
dpkg-source --no-check is appropriate only after independently checking the
locally produced source artifact checksums. Lint both source and binary
.changes with lintian --fail-on error. Keep logs and all artifacts outside
the checkout. A second independent extraction/build checks rebuildability;
bit-for-bit reproducibility is a separate claim requiring comparison.
Safe smoke test
---------------
Install the resulting binary in a disposable container with no USB mappings,
no host networking and no privileged mode. Suppress service startup using
policy-rc.d before installing g15daemon. Run debian/tests/help, which compares
timeout-bounded --help and -h output. These exit before any daemon connection,
sensor access or keyboard thread. Do not invoke the normal client or -d.
The same test is declared as a superficial autopkgtest.
Scope and limitations
---------------------
No service/init, udev rule, keyboard macro or automatic startup is shipped.
The existing runtime code has not been safety-certified or rewritten: legacy
sensor paths, argument parsing, array bounds, string handling and threading
need separate runtime review. Build/CLI tests do not validate hardware, LCD
rendering, live daemon interaction, battery support or other architectures.
Use --info-rotate, not the incorrectly documented short form -ir; see the
manual for the existing parser discrepancy.
+48
View File
@@ -0,0 +1,48 @@
g15stats (3.0.3+git20240609.95de11a-1~local1) UNRELEASED; urgency=medium
* Add cached multi-vendor GPU summary columns: independent read-only amdgpu
workers and optional, deadline-bounded batched shell-free nvidia-smi
queries.
Collect all supported cards by default, display two columns concurrently
beside compact CPU/RAM data, and cycle larger inventories in sorted pairs.
Document PCI selection, bounds, units, missing metrics and unchanged no-GPU
layouts; add multi-adapter isolation, live collector and framebuffer tests.
Check complete GPU/TOTAL/footer glyphs against font bits independently;
exercise off/single/paged layouts and verify lossless visual exports.
* Initialize the bitmap canvas locally, avoiding the optional FreeType
canvas ABI mismatch that corrupts the heap during application startup.
Add a full-main regression with isolated daemon transport.
* Import Debian 1.9.2-2 packaging and manual, retaining its changelog,
attribution and checksum-recorded archive provenance.
* Package Menelkir master 95de11a526346b3d9fd33df051b330c36b1c8a16,
a post-3.0.3 snapshot, rather than mislabelling this fork as 1.9.2.
* Use debhelper compat 13, 3.0 (quilt), direct build dependencies,
rootless builds and all Debian hardening flags on Debian 13.
* Quilt build fixes preserve compiler flags, link libgtop correctly,
remove the bare include flag and align the embedded snapshot version.
* Extend the archived manual and add isolated CLI smoke tests.
Do not install init scripts or automatically start the client.
-- Mikhail Yevchenko <m.ṥῥẚɱ.ѓѐḿởύḙ@uo1.net> Mon, 07 Sep 2026 15:47:05 +0300
g15stats (1.9.2-2) unstable; urgency=low
* Supports binutils-gold linker, writing explicitly our library
dependencies. Patch from Bhavani Shankar (Closes: #554382).
-- Giacomo Catenazzi <cate@debian.org> Tue, 15 Mar 2011 08:04:59 +0100
g15stats (1.9.2-1) unstable; urgency=low
* New upstream version (Closes: #530548).
* Add support for automake and autoconf
-- Giacomo Catenazzi <cate@debian.org> Tue, 04 Aug 2009 13:15:38 +0200
g15stats (1.0-1) unstable; urgency=low
* Initial release. (Closes: #439792).
-- Giacomo Catenazzi <cate@debian.org> Tue, 18 Mar 2008 08:09:06 +0100
+1
View File
@@ -0,0 +1 @@
13
+38
View File
@@ -0,0 +1,38 @@
Source: g15stats
Section: utils
Priority: optional
Maintainer: Mikhail Yevchenko <m.ṥῥẚɱ.ѓѐḿởύḙ@uo1.net>
Build-Depends: debhelper (>= 13~),
autoconf,
automake,
libg15-dev,
libg15daemon-client-dev,
libg15render-dev,
libgtop2-dev,
libtool,
pkgconf
Standards-Version: 4.7.2
Rules-Requires-Root: no
Homepage: https://gitlab.com/menelkir/g15stats
Package: g15stats
Architecture: linux-any
Depends: g15daemon, ${shlibs:Depends}, ${misc:Depends}
Description: system statistics display client for G15Daemon
Displays system statistics on the LCD of supported Logitech keyboards
through g15daemon.
.
CPU Screen displays graphs of User/System/Nice and Idle time, along with
LoadAVG and Uptime.
.
Memory Screen displays Memory Total & Free, and graph of Used vs
Buffered+Cached Memory.
.
Swap Screen displays Used, Free and Total swap space, along with the number
of pages currently paged in/out.
.
Network Screen displays Total bytes In/Out, history graph, Peak speed.
.
Additional screens show CPU frequency, battery charge, temperature and
fan speed where supported by the system's sensors.
+40
View File
@@ -0,0 +1,40 @@
This package was debianized by Giacomo A. Catenazzi <cate@debian.org> on
Sat, 15 Sep 2007 08:32:51 +0200.
It was downloaded from http://g15daemon.sourceforge.net/
Upstream Authors (also see AUTHORS):
Mike Lampard <mlampard@users.sourceforge.net>
Antonio Bartolini <robynhub@gmail.com>
Piotr Czarnecki <czarnyckm@users.sf.net>
Daniel Menelkir <dmenelkir@gmail.com>
Copyright:
Copyright (c) 2006-2007 Mike Lampard
Copyright (c) 2008-2010 Mike Lampard (g15stats.c)
License:
g15daemon is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
g15daemon is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
License can be found in the file `/usr/share/common-licenses/GPL-2'.
The Debian packaging is (C) 2008, Giacomo A. Catenazzi <cate@debian.org> and
is licensed under the GPL v2 (or later), see `/usr/share/common-licenses/GPL-2'.
Local packaging additions are Copyright (c) 2026 Mikhail Yevchenko
<m.ṥῥẚɱ.ѓѐḿởύḙ@uo1.net>, licensed under GPL v2 or later.
The imported manual names Mike Lampard as author; its Debian archive origin
and subsequent local updates are documented in debian/PROVENANCE.
The original Debian diff, including the original copyright notice, is retained
unchanged in debian/provenance/. No authorship of the fork is claimed here.
Vendored
+4
View File
@@ -0,0 +1,4 @@
README
AUTHORS
debian/README.Debian
debian/PROVENANCE
+119
View File
@@ -0,0 +1,119 @@
.\" Imported from Debian 1.9.2-2; see debian/PROVENANCE for attribution.
.TH "g15stats" "1" "7 September 2026" "G15Daemon - g15stats" "G15Daemon User Manual"
.SH "NAME"
g15stats \- system statistics display client for G15Daemon
.SH "SYNOPSIS"
.B g15stats
.RI [ options ]
.SH "DESCRIPTION"
The program provides usage meters for the LCD on
supported Logitech keyboards, using g15daemon:
.PP
CPU Screen displays graphs of User/System/Nice and Idle time, along with
LoadAVG and Uptime.
.PP
Memory Screen displays Memory Total & Free, and graph of Used vs
Buffered+Cached Memory.
.PP
Swap Screen displays Used, Free and Total swap space, along with the number
of pages currently paged in/out.
.PP
Network Screen displays Total bytes In/Out, history graph, Peak speed.
.PP
The summary, CPU frequency, battery, temperature and fan screens display
additional statistics where the relevant system sensors are available.
.SH "OPTIONS"
g15stats understands the following options:
.TP
.BI "\-gpu, --gpu " "auto|off|dddd:bb:ss.f"
Select the GPU summary columns. Default auto collects all PCI addresses
bound to amdgpu or nvidia at startup, including mixed vendors; off disables collection and retains
the original layout. A PCI address selects one card explicitly.
No supported card in auto mode leaves the original layout intact; an explicit
missing card shows N/A. Restart after hotplug to rediscover devices.
Intel, nouveau and radeon are not supported.
.br
.P
.HP
\-h,--help show usage information.
.P
.HP
\-d,--daemon run in background.
.P
.HP
\-i,--interface [interface] monitor network interface [interface] ie -i eth0.
.P
.HP
\-nsa,--net-scale-absolute scale net graphs against maximum speed seen.
.TP
.BR \-u , " --unicore"
Display unicore graphs on the CPU screen.
.TP
.BI "\-r, --refresh " seconds
Set the refresh interval from 1 to 300 seconds (default: 1).
.TP
.BI "\-t, --temperature " id
Select the temperature hwmon sensor (0 to 3).
.TP
.BI "\-f, --fan " id
Select the fan hwmon sensor (0 to 3).
.TP
.BI "\-gt, --global-temp " id
Show this temperature probe instead of the maximum on the summary screen.
.TP
.BR \-df , " --disable-freq"
Disable CPU frequency monitoring.
.TP
.B --info-rotate
Rotate the bottom information bar. Use the long option: the fork documents
\-ir in its help, but its parser checks \-rc and also treats that prefix as
\-r. The documented short form \-ir is not supported correctly.
.TP
.BR \-vc , " --variable-cpu"
Recalculate the CPU count for systems supporting CPU hotplug.
.SH "KEYS"
L2 selects the previous screen; L3 selects the next screen.
L4 selects an alternative view where available; L5 changes the bottom bar.
.SH "NOTES"
Each GPU column shows vendor/index and PCI bus:slot.function, UTL percent,
U used VRAM, T total VRAM, and TEM in C.
Used and total occupy separate rows with explicit MiB/GiB units (TiB/PiB
if needed), rounded to one decimal. On AMD APUs VRAM can mean only the reserved
carveout. With one GPU the summary retains its 4/5 indicators and shortened
graphs. With two GPUs both columns appear together beside compact CPU percent,
R used system RAM, T total system RAM and SWP percent (plus GPU count in five-row
mode). RAM has explicit units too. More than two cycle in PCI-sorted pairs every
five seconds from startup; all cards continue collecting. NV1/AMD2-style indices
are one-based selected-inventory positions, not NVIDIA ordinals; startup diagnostics
map them to full PCI addresses. The 64-card bound fails explicitly on overflow.
The bottom bar and other screens are unchanged. See README for pixel coordinates.
.PP
AMD reads device-local sysfs/hwmon metrics. NVIDIA optionally uses
/usr/bin/nvidia-smi with no shell or SDK dependency. Independent AMD workers and
one batched NVIDIA worker wait two seconds between queries. All NVIDIA cards
share one PCI-keyed query with a 750ms deadline;
timeouts kill the child, and an unreaped child prevents additional launches.
The child is also killed on parent death. Data older than five seconds and
unavailable metrics show N/A, never fabricated zero. Display refresh remains
controlled by the refresh option. Worker shutdown has one shared one-second
join/reap budget; a stuck kernel read can outlive it until process exit.
No GPU settings are written.
.PP
Normal operation requires a running g15daemon. This package does not install
or enable a g15stats service. Run the client as an ordinary user.
The help options exit before connecting to g15daemon.
Sensor availability depends on the kernel and hardware; legacy battery
paths may be unavailable on modern systems.
.SH "AUTHOR"
Mike Lampard
.SH "SEE ALSO"
.BR g15daemon (1),
.BR libg15 (3)
+1
View File
@@ -0,0 +1 @@
usr/bin/g15stats
+1
View File
@@ -0,0 +1 @@
debian/g15stats.1
+47
View File
@@ -0,0 +1,47 @@
Description: Preserve Debian flags and use conventional automake linking
Remove the bare -I (which consumes the next compiler argument), pass GTOP
libraries after objects, and let debhelper install selected documentation.
The historical Bhavani Shankar explicit pthread/math linkage fix is already
present in this fork and remains intact. Align stale configure versions with
the pinned post-3.0.3 snapshot (see debian/PROVENANCE).
Author: Mikhail Yevchenko <m.ṥῥẚɱ.ѓѐḿởύḙ@uo1.net>
Forwarded: not-needed
Last-Update: 2026-09-07
--- a/configure.ac
+++ b/configure.ac
@@ -1,7 +1,5 @@
-AC_INIT([g15stats],[3.0],[dmenelkir@gmail.com])
+AC_INIT([g15stats],[3.0.3+git20240609.95de11a],[dmenelkir@gmail.com])
-PACKAGE=g15stats
-VERSION=3.0.1
AC_PREFIX_DEFAULT(/usr)
AC_CONFIG_AUX_DIR(config)
-AM_INIT_AUTOMAKE($PACKAGE, $VERSION)
+AM_INIT_AUTOMAKE([foreign])
@@ -26,6 +24,4 @@
CPPFLAGS="$CPPFLAGS $GTOP_CFLAGS"
-CFLAGS="$CPPFLAGS $GTOP_CFLAGS"
-LDFLAGS="$LDFLAGS $GTOP_LIBS"
AC_CONFIG_FILES([Makefile])
AC_OUTPUT
--- a/Makefile.am
+++ b/Makefile.am
@@ -2,12 +2,8 @@
-AM_CFLAGS = -Wall @CFLAGS@ -I$(top_builddir) -I$(top_srcdir)
+AM_CFLAGS = -Wall
+AM_CPPFLAGS = -I$(top_builddir) -I$(top_srcdir)
METASOURCES = AUTO
bin_PROGRAMS = g15stats
noinst_HEADERS = g15stats.h
-docdir = @docdir@
-doc_DATA = README ChangeLog AUTHORS NEWS COPYING
-
-LIBS = -lg15daemon_client -lg15render -lpthread -lm -lrt
-IMCLUDES = -I
-g15stats_LDFLAGS = -avoid-version
+g15stats_LDADD = -lg15daemon_client -lg15render -lpthread -lm -lrt $(GTOP_LIBS)
g15stats_SOURCES = g15stats.c g15stats.h
+1
View File
@@ -0,0 +1 @@
modern-build.patch
Binary file not shown.
+29
View File
@@ -0,0 +1,29 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Format: 1.0
Source: g15stats
Binary: g15stats
Architecture: any
Version: 1.9.2-2
Maintainer: Giacomo Catenazzi <cate@debian.org>
Homepage: http://www.g15tools.com
Standards-Version: 3.8.2
Build-Depends: cdbs, debhelper (>= 5), autotools-dev, libxtst-dev, libfreetype6-dev, libusb-dev, libgtop2-dev, libg15-dev, libg15render-dev (>= 1.2-3), libg15daemon-client-dev, automake, libtool, autoconf
Checksums-Sha1:
b53ea7414bd47b68a73aaa8f548d36f50b47aa09 25190 g15stats_1.9.2.orig.tar.gz
245c96ab8267a31c5e1ebebe91307e8ff99bb976 2316 g15stats_1.9.2-2.diff.gz
Checksums-Sha256:
9dda49a3f09e66ce738ceecb07f2c0fef7ecd5ecbd06e435b340dfca35a37da4 25190 g15stats_1.9.2.orig.tar.gz
ce724380069ee62563f2898b1bbd0afb80bb632cb6dbf3d1747871f7cdfefbb3 2316 g15stats_1.9.2-2.diff.gz
Files:
beb285f739127e91b97af465e6b53c4a 25190 g15stats_1.9.2.orig.tar.gz
bec0a80e18f8905d4d70a1c9fc575043 2316 g15stats_1.9.2-2.diff.gz
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.11 (GNU/Linux)
iEYEARECAAYFAk1/EkEACgkQ+ZNUJLHfmlclzQCeMMSD3P7ix2G7M8JJnlfTbqBS
4U4AnjhLSSPFun2rWQirQ5w9D7ubM3gC
=ounU
-----END PGP SIGNATURE-----
Vendored Executable
+10
View File
@@ -0,0 +1,10 @@
#!/usr/bin/make -f
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
%:
dh $@
override_dh_auto_install:
dh_auto_install --destdir=debian/tmp
+1
View File
@@ -0,0 +1 @@
3.0 (quilt)
+1
View File
@@ -0,0 +1 @@
debian/provenance/g15stats_1.9.2-2.diff.gz
+3
View File
@@ -0,0 +1,3 @@
Tests: help
Depends: @
Restrictions: superficial
Vendored Executable
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh
set -eu
# Help returns before daemon connection, sensor polling or keyboard threads.
# Run only in an isolated testbed without USB device passthrough.
work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT HUP INT TERM
timeout 5 g15stats --help > "$work/long"
timeout 5 g15stats -h > "$work/short"
cmp "$work/long" "$work/short"
grep -F 'g15stats 3.0.3+git20240609.95de11a' "$work/long"
grep -F -- '--interface' "$work/long"
grep -F -- '--refresh' "$work/long"
+5
View File
@@ -0,0 +1,5 @@
version=4
# Local post-3.0.3 snapshot: see PROVENANCE; no upstream signatures available.
opts="mode=git, pgpmode=none, dversionmangle=s/\+git.*//" \
https://gitlab.com/menelkir/g15stats.git refs/tags/@ANY_VERSION@
+15
View File
@@ -0,0 +1,15 @@
pkgbase = g15stats-openrc
pkgdesc = g15stats init script for OpenRC
pkgver = 20211101
pkgrel = 1
url = https://gitlab.com/menelkir/g15stats
arch = any
license = GPL2
depends = g15stats
depends = g15daemon-openrc
source = https://gitlab.com/menelkir/g15stats/-/raw/master/contrib/init/g15stats.initd
source = https://gitlab.com/menelkir/g15stats/-/raw/master/contrib/init/g15stats.confd
sha256sums = 136da674e6e7c993dd388e09b245dc43d25fefebb1e379092585cbf87bf55676
sha256sums = ca6520f9752ec526e284d4359aea57399949e0ea5e98ee5904524d186d898f01
pkgname = g15stats-openrc
+22
View File
@@ -0,0 +1,22 @@
# Maintainer: Daniel Menelkir <dmenelkir at gmail dot com>
pkgname=g15stats-openrc
pkgver=20211101
pkgrel=1
pkgdesc="g15stats init script for OpenRC"
arch=('any')
url="https://gitlab.com/menelkir/g15stats"
license=('GPL2')
depends=('g15stats' 'g15daemon-openrc')
_filename=('g15stats.initd' 'g15stats.confd')
source=("https://gitlab.com/menelkir/g15stats/-/raw/master/contrib/init/g15stats.initd"
"https://gitlab.com/menelkir/g15stats/-/raw/master/contrib/init/g15stats.confd")
sha256sums=('136da674e6e7c993dd388e09b245dc43d25fefebb1e379092585cbf87bf55676'
'ca6520f9752ec526e284d4359aea57399949e0ea5e98ee5904524d186d898f01')
package() {
install -dm755 "$pkgdir/etc/init.d"
install -dm755 "$pkgdir/etc/conf.d"
install -Dm755 "$srcdir/g15stats.initd" "$pkgdir/etc/init.d/g15stats"
install -Dm644 "$srcdir/g15stats.confd" "$pkgdir/etc/conf.d/g15stats"
}
+13
View File
@@ -0,0 +1,13 @@
pkgbase = g15stats-systemd
pkgdesc = g15stats init script for systemd
pkgver = 20211101
pkgrel = 1
url = https://gitlab.com/menelkir/g15stats
arch = any
license = GPL2
depends = g15stats
depends = g15daemon-systemd
source = https://gitlab.com/menelkir/g15stats/-/raw/master/contrib/init/g15stats.service
sha256sums = 2723465ba726a4675d442fdb3068353def37bc70ce84cdab6c65f1c450c1dc4a
pkgname = g15stats-systemd
+17
View File
@@ -0,0 +1,17 @@
# Maintainer: Daniel Menelkir <dmenelkir at gmail dot com>
pkgname=g15stats-systemd
pkgver=20211101
pkgrel=1
pkgdesc="g15stats init script for systemd"
arch=('any')
url="https://gitlab.com/menelkir/g15stats"
license=('GPL2')
depends=('g15stats' 'g15daemon-systemd')
_filename='g15stats.run'
source=("https://gitlab.com/menelkir/g15stats/-/raw/master/contrib/init/g15stats.service")
sha256sums=('2723465ba726a4675d442fdb3068353def37bc70ce84cdab6c65f1c450c1dc4a')
package() {
install -D -m 644 "g15stats.service" "${pkgdir}/usr/lib/systemd/system/g15stats.service"
}
+17
View File
@@ -0,0 +1,17 @@
pkgbase = g15stats
pkgdesc = A CPU/Mem/Swap/Network monitoring client for G15Daemon.
pkgver = 3.0.3
pkgrel = 2
url = https://gitlab.com/menelkir/g15stats
arch = i686
arch = x86_64
license = GPL
depends = g15daemon
depends = libgtop
depends = libg15
depends = libg15render
source = https://gitlab.com/menelkir/g15stats/-/archive/3.0.3/g15stats-3.0.3.tar.bz2
sha512sums = 6034fec8f2188182c6fd7587ec5f5ef8d06b617875573382385edc12f1aefb3b9bf52b437eafb7c9412676c5ceae42ae8c9c2099270908aea2b949d01df79047
pkgname = g15stats
+32
View File
@@ -0,0 +1,32 @@
# Maintainer: Daniel Menelkir <dmenelkir at gmail dot com>
# Contributor: Mathieu OTHACEHE <m.othacehe@gmail.com>
# Contributor: Brenton <brenton@taylorbyte.com>
pkgname=g15stats
pkgver=3.0.3
pkgrel=2
pkgdesc="A CPU/Mem/Swap/Network monitoring client for G15Daemon."
arch=('i686' 'x86_64')
url="https://gitlab.com/menelkir/g15stats"
license=('GPL')
depends=('g15daemon' 'libgtop' 'libg15' 'libg15render')
source=(https://gitlab.com/menelkir/$pkgname/-/archive/$pkgver/$pkgname-$pkgver.tar.bz2)
sha512sums=('6034fec8f2188182c6fd7587ec5f5ef8d06b617875573382385edc12f1aefb3b9bf52b437eafb7c9412676c5ceae42ae8c9c2099270908aea2b949d01df79047')
build() {
cd $pkgname-$pkgver
./configure --prefix=/usr \
--sysconfdir=/etc \
--localstatedir=/var
make
}
package() {
cd $pkgname-$pkgver
make DESTDIR="$pkgdir" install
echo ""
echo "This package doesn't come with an init, you should install one:"
echo "g15stats-runit"
echo "g15stats-openrc"
echo "g15stats-systemd"
}
+33
View File
@@ -0,0 +1,33 @@
PORTNAME= g15stats
PORTVERSION= 3.0.3
CATEGORIES= sysutils
MASTER_SITES= https://gitlab.com/menelkir/${PORTNAME}/-/archive/${PORTVERSION}/
MAINTAINER= dmenelkir@gmail.com
COMMENT= Resource monitor for G15daemon
LICENSE= GPLv2
LIB_DEPENDS+= libg15.so:sysutils/libg15 \
libg15render.so:sysutils/libg15render \
libgtop-2.0.so:devel/libgtop \
libg15daemon_client.so:sysutils/g15daemon
USES= gmake localbase libtool gnome pkgconfig
USE_LDCONFIG= yes
USE_GNOME+= glib20
GNU_CONFIGURE= yes
CFLAGS+= -std=c99 -fcommon
USE_RC_SUBR= g15stats
INSTALL_TARGET= install-strip
PLIST_FILES= bin/g15stats \
etc/rc.d/g15stats
.include <bsd.port.mk>
+3
View File
@@ -0,0 +1,3 @@
TIMESTAMP = 1621364357
SHA256 (g15stats-3.0.3.tar.gz) = 000ad7852fcf5b2d9524ea4fb0799654341b4ea23e076848e3b96dcb7d41d250
SIZE (g15stats-3.0.3.tar.gz) = 363805
+26
View File
@@ -0,0 +1,26 @@
#!/bin/sh
# $FreeBSD$
#
# PROVIDE: g15stats
# REQUIRE: LOGIN g15daemon
# KEYWORD: shutdown
#
# Add these lines to /etc/rc.conf.local or /etc/rc.conf
# to enable this service:
#
# g15stats_enable (bool): Set to NO by default.
# Set it to YES to enable doormand.
. /etc/rc.subr
name=g15stats
rcvar="${name}_enable"
load_rc_config $name
: ${g15stats_enable:="NO"}
command=%%PREFIX%%/bin/${name}
command_args='-d -i re0'
run_rc_command "$1"
+3
View File
@@ -0,0 +1,3 @@
Resource Monitor for G15Daemon.
WWW: https://gitlab.com/menelkir/g15stats
+1
View File
@@ -0,0 +1 @@
DIST g15stats-3.0.2.tar.bz2 295556 BLAKE2B d2e96e9d715f7dde5ec6227f5fe6d6d110bfd80d33a831ec1dffda754c4c606fce1134502cb44de691303e2f4f0d7960f806ef37c82002de896a12c90dc0b3b0 SHA512 d0b0aaca5cb551a77ce09e63bf903764cafc060636d0d19baae245e9b397926f15d776aa08076dc71aafaf2c5aa064fea77f589d10fec908c8543217ff3a3d69
+46
View File
@@ -0,0 +1,46 @@
# Copyright 2021 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
EAPI=7
inherit autotools
DESCRIPTION="CPU, memory, swap, network stats for G15 Keyboard"
HOMEPAGE="https://gitlab.com/menelkir/${PN}"
SRC_URI="https://gitlab.com/menelkir/${PN}/-/archive/${PV}/${PN}-${PV}.tar.bz2"
LICENSE="GPL-2"
SLOT="0"
KEYWORDS="~amd64 ~ppc ~ppc64 ~x86"
IUSE=""
RDEPEND="app-misc/g15daemon
dev-libs/libg15
dev-libs/libg15render
sys-libs/zlib
gnome-base/libgtop"
DEPEND="${RDEPEND}"
BDEPEND="virtual/pkgconfig"
src_prepare() {
default
}
src_configure() {
export CPPFLAGS="${CFLAGS}"
econf
}
src_install() {
default
rm "${D}"/usr/share/doc/${PF}/{COPYING,NEWS} || die
newconfd "${S}/contrib/init/${PN}.confd" ${PN}
newinitd "${S}/contrib/init/${PN}.initd" ${PN}
}
pkg_postinst() {
elog "Remember to set the interface you want monitored in"
elog "/etc/conf.d/g15stats"
}
+48
View File
@@ -0,0 +1,48 @@
# Copyright 2021 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
EAPI=7
inherit autotools git-r3
DESCRIPTION="CPU, memory, swap, network stats for G15 Keyboard"
HOMEPAGE="https://gitlab.com/menelkir/${PN}"
EGIT_REPO_URI="https://gitlab.com/menelkir/${PN}.git"
LICENSE="GPL-2"
SLOT="0"
IUSE=""
KEYWORDS=""
RDEPEND="app-misc/g15daemon
dev-libs/libg15
dev-libs/libg15render
sys-libs/zlib
gnome-base/libgtop"
DEPEND="${RDEPEND}"
BDEPEND="virtual/pkgconfig"
src_prepare() {
default
mv configure.{in,ac} || die
eautoreconf
}
src_configure() {
export CPPFLAGS="${CFLAGS}"
econf
}
src_install() {
default
rm "${D}"/usr/share/doc/${PF}/{COPYING,NEWS} || die
newconfd "${S}/contrib/init/${PN}.confd" ${PN}
newinitd "${S}/contrib/init/${PN}.initd" ${PN}
}
pkg_postinst() {
elog "Remember to set the interface you want monitored in"
elog "/etc/conf.d/g15stats"
}
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE pkgmetadata SYSTEM "http://www.gentoo.org/dtd/metadata.dtd">
<pkgmetadata>
<maintainer type="person">
<email>robbat2@gentoo.org</email>
</maintainer>
<maintainer type="person">
<email>polynomial-c@gentoo.org</email>
<name>Lars Wendler</name>
</maintainer>
<upstream>
<remote-id type="sourceforge">g15daemon</remote-id>
</upstream>
</pkgmetadata>
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
exec 2>&1
sv check g15daemon >/dev/null || exit 1
exec g15stats
+18
View File
@@ -0,0 +1,18 @@
# Template file for 'g15stats'
pkgname=g15stats
version=3.0.3
revision=1
build_style=gnu-configure
CFLAGS=" -fcommon" # https://wiki.gentoo.org/wiki/Gcc_10_porting_notes/fno_common
makedepends="libgtop-devel g15daemon pkg-config"
depends="libgtop g15daemon"
short_desc="An usage monitor for G15daemon"
maintainer="Daniel Menelkir <dmenelkir@gmail.com>"
license="GPL-2.0-or-later"
homepage="https://gitlab.com/menelkir/g15stats"
distfiles="${homepage}/-/archive/${version}/g15stats-${version}.tar.bz2"
checksum=f5bb77d4b8a1030d07efc22919446c4abf876ca3086ff7c50cfcae9d67b64647
post_install() {
vsv g15stats
}
+119 -4
View File
@@ -55,6 +55,53 @@ This is a simple stats client showing graphs for CPU, MEM & Swap usage, Network
#include <glibtop/uptime.h> #include <glibtop/uptime.h>
#include <glibtop/sysinfo.h> #include <glibtop/sysinfo.h>
#include "g15stats.h" #include "g15stats.h"
#include "gpu.h"
/* Main-thread-only summary geometry; other screens retain their full width. */
static int summary_gpu;
static void init_canvas(g15canvas *canvas)
{
/* We use bitmap fonts only. libg15render's g15r_initCanvas can write a
* FreeType handle beyond sizeof(g15canvas) when the library was built
* with TTF_SUPPORT but its consumer was not. Initialize our own storage
* instead; the bitmap API uses only the common buffer/mode prefix. */
memset(canvas, 0, sizeof(*canvas));
}
static void gpu_lines(size_t index, gpu_sample sample, char lines[5][32])
{
char memory[24];
snprintf(lines[0], 32, "%s%zu %.7s",
gpu_devices[index].backend == 1 ? "AMD" : gpu_devices[index].backend == 2 ? "NV" : "GPU",
index + 1, gpu_devices[index].bus + 5);
snprintf(lines[1], sizeof(lines[1]), "UTL N/A");
if (sample.busy >= 0)
snprintf(lines[1], sizeof(lines[1]), "UTL %lld%%", sample.busy);
gpu_memory(memory, sizeof(memory), sample.used);
snprintf(lines[2], sizeof(lines[2]), "U %s", memory);
gpu_memory(memory, sizeof(memory), sample.total);
snprintf(lines[3], sizeof(lines[3]), "T %s", memory);
snprintf(lines[4], sizeof(lines[4]), "TEM N/A");
if (sample.temp != LLONG_MIN)
snprintf(lines[4], sizeof(lines[4]), "TEM %lldC", sample.temp);
}
static void draw_gpu_column(g15canvas *canvas)
{
gpu_frame frame = gpu_snapshot();
size_t first = gpu_display_first(frame.count, gpu_milliseconds());
size_t i;
for (i = first; i < frame.count && i < first + 2; i++) {
char lines[5][32];
int row, x = frame.count == 1 ? 108 : 54 + (int)(i - first) * 54;
gpu_lines(i, frame.samples[i], lines);
g15r_drawLine(canvas, x - 3, 0, x - 3, 34, G15_COLOR_BLACK);
for (row = 0; row < 5; row++)
g15r_renderString(canvas, (unsigned char *)lines[row], 0,
G15_TEXT_SMALL, x, 1 + row * 7);
}
}
int g15screen_fd; int g15screen_fd;
@@ -213,7 +260,7 @@ void print_vert_label_logic(g15canvas *canvas, char *label, unsigned int sx){
} }
void print_vert_label(g15canvas *canvas, char *label){ void print_vert_label(g15canvas *canvas, char *label){
print_vert_label_logic(canvas, label, TEXT_RIGHT); print_vert_label_logic(canvas, label, summary_gpu ? 98 : TEXT_RIGHT);
} }
void drawBar_reversed (g15canvas * canvas, int x1, int y1, int x2, int y2, int color, void drawBar_reversed (g15canvas * canvas, int x1, int y1, int x2, int y2, int color,
@@ -264,8 +311,9 @@ void drawBar_reversed (g15canvas * canvas, int x1, int y1, int x2, int y2, int c
} }
void drawBar_both(g15canvas *canvas, int y1, int y2, int bar_current, int bar_total, int rbar_current, int rbar_total) { void drawBar_both(g15canvas *canvas, int y1, int y2, int bar_current, int bar_total, int rbar_current, int rbar_total) {
g15r_drawBar(canvas, BAR_START, y1, BAR_END, y2, G15_COLOR_BLACK, bar_current, bar_total, 4); int end = summary_gpu ? 96 : BAR_END;
drawBar_reversed(canvas, BAR_START, y1, BAR_END, y2, G15_COLOR_BLACK, rbar_current, rbar_total, 5); g15r_drawBar(canvas, BAR_START, y1, end, y2, G15_COLOR_BLACK, bar_current, bar_total, 4);
drawBar_reversed(canvas, BAR_START, y1, end, y2, G15_COLOR_BLACK, rbar_current, rbar_total, 5);
} }
void drawLine_both(g15canvas *canvas, int y1, int y2) { void drawLine_both(g15canvas *canvas, int y1, int y2) {
@@ -826,6 +874,42 @@ void draw_cpu_screen_unicore(g15canvas *canvas, char *tmpstr, int drawgraph, int
draw_cpu_screen_unicore_logic(canvas, cpu, tmpstr, drawgraph, printlabels, 0); draw_cpu_screen_unicore_logic(canvas, cpu, tmpstr, drawgraph, printlabels, 0);
} }
/* Two 52px GPU columns leave 51px for a compact CPU/system-RAM summary.
* Keep this separate from the legacy geometry and generic sensor probing. */
static void compact_ram_lines(char lines[5][32], long long used, long long total)
{
char value[24];
gpu_memory(value, sizeof(value), used);
snprintf(lines[1], 32, "R %s", value);
gpu_memory(value, sizeof(value), total);
snprintf(lines[2], 32, "T %s", value);
}
static void draw_compact_summary(g15canvas *canvas)
{
glibtop_cpu cpu;
glibtop_mem mem;
glibtop_swap swap;
static guint64 last_total, last_idle;
char lines[5][32];
int row, rows = mode[SCREEN_SUMMARY] ? 4 : 5;
glibtop_get_cpu(&cpu);
glibtop_get_mem(&mem);
glibtop_get_swap(&swap);
snprintf(lines[0], 32, "CPU N/A");
if (cpu.total > last_total && cpu.idle >= last_idle && cpu.idle - last_idle <= cpu.total - last_total)
snprintf(lines[0], 32, "CPU %.0f%%", 100.0 * (1.0 - (double)(cpu.idle - last_idle) / (cpu.total - last_total)));
last_total = cpu.total;
last_idle = cpu.idle;
compact_ram_lines(lines, mem.total - mem.free, mem.total);
snprintf(lines[3], 32, "SWP %.0f%%", swap.total ? 100.0 * swap.used / swap.total : 0.0);
snprintf(lines[4], 32, "GPUs %zu", gpu_count);
g15r_clearScreen(canvas, G15_COLOR_WHITE);
for (row = 0; row < rows; row++)
g15r_renderString(canvas, (unsigned char *)lines[row], 0,
G15_TEXT_SMALL, 1, 1 + row * (rows == 5 ? 7 : 9));
}
void draw_cpu_screen_multicore(g15canvas *canvas, char *tmpstr, int unicore) { void draw_cpu_screen_multicore(g15canvas *canvas, char *tmpstr, int unicore) {
glibtop_cpu cpu; glibtop_cpu cpu;
int core,ncpumax; int core,ncpumax;
@@ -839,6 +923,10 @@ void draw_cpu_screen_multicore(g15canvas *canvas, char *tmpstr, int unicore) {
b_nice[GLIBTOP_NCPU],b_sys[GLIBTOP_NCPU],b_idle[GLIBTOP_NCPU], b_nice[GLIBTOP_NCPU],b_sys[GLIBTOP_NCPU],b_idle[GLIBTOP_NCPU],
b_irq[GLIBTOP_NCPU],b_iowait[GLIBTOP_NCPU]; b_irq[GLIBTOP_NCPU],b_iowait[GLIBTOP_NCPU];
if (summary_gpu && gpu_count > 1 && cycle == SCREEN_SUMMARY) {
draw_compact_summary(canvas);
return;
}
init_cpu_count(); init_cpu_count();
ncpumax = ncpu; ncpumax = ncpu;
@@ -1694,8 +1782,21 @@ int main(int argc, char *argv[]){
unsigned char interface[128]; unsigned char interface[128];
static char tmpstr[MAX_LINES]; static char tmpstr[MAX_LINES];
int unicore = 0; int unicore = 0;
const char *gpu_selection = "auto";
for (i=0;i<argc;i++) { for (i=0;i<argc;i++) {
if (!strcmp(argv[i], "--gpu") || !strcmp(argv[i], "-gpu")) {
if (++i >= argc) {
fprintf(stderr, "--gpu requires auto, off or a PCI address\n");
return 1;
}
gpu_selection = argv[i];
if (!gpu_selection_valid(gpu_selection)) {
fprintf(stderr, "Invalid GPU selection; use auto, off or dddd:bb:ss.f\n");
return 1;
}
continue;
}
if(0==strncmp(argv[i],"-d",2)||0==strncmp(argv[i],"--daemon",8)) { if(0==strncmp(argv[i],"-d",2)||0==strncmp(argv[i],"--daemon",8)) {
go_daemon=1; go_daemon=1;
} }
@@ -1722,6 +1823,8 @@ int main(int argc, char *argv[]){
printf("%s %s - (c) 2008-2010 Mike Lampard, Piotr Czarnecki\n",PACKAGE_NAME,VERSION); printf("%s %s - (c) 2008-2010 Mike Lampard, Piotr Czarnecki\n",PACKAGE_NAME,VERSION);
printf("Usage: %s [Options]\n", PACKAGE_NAME); printf("Usage: %s [Options]\n", PACKAGE_NAME);
printf("Options:\n"); printf("Options:\n");
printf("--gpu (-gpu) [auto|off|dddd:bb:ss.f] GPU columns (default: auto, all supported GPUs).\n");
printf(" Two GPUs side by side; more cycle in PCI-sorted pairs every 5 seconds.\n");
printf("--daemon (-d) run in background\n"); printf("--daemon (-d) run in background\n");
printf("--unicore (-u) display unicore graphs only on the CPU screen\n"); printf("--unicore (-u) display unicore graphs only on the CPU screen\n");
printf("--help (-h) this help text.\n"); printf("--help (-h) this help text.\n");
@@ -1785,6 +1888,13 @@ int main(int argc, char *argv[]){
} }
} }
} }
if (gpu_detect(gpu_selection)) {
fprintf(stderr, "GPU enumeration failed or exceeds %d adapters; use --gpu PCI or off\n", GPU_MAX);
return 1;
}
for (size_t gpu = 0; gpu < gpu_count; gpu++)
fprintf(stderr, "GPU %zu: %s (%s)\n", gpu + 1, gpu_devices[gpu].bus,
gpu_devices[gpu].backend == 1 ? "AMD" : gpu_devices[gpu].backend == 2 ? "NV" : "unsupported/missing");
if((g15screen_fd = new_g15_screen(G15_G15RBUF))<0){ if((g15screen_fd = new_g15_screen(G15_G15RBUF))<0){
printf("Sorry, cant connect to the G15daemon\n"); printf("Sorry, cant connect to the G15daemon\n");
return -1; return -1;
@@ -1795,11 +1905,12 @@ int main(int argc, char *argv[]){
daemonise(0,0); daemonise(0,0);
if(canvas != NULL) if(canvas != NULL)
g15r_initCanvas(canvas); init_canvas(canvas);
else else
return -1; return -1;
glibtop_init(); glibtop_init();
if (gpu_start()) fprintf(stderr, "GPU collector unavailable; affected cards display N/A\n");
pthread_create(&keys_thread,NULL,(void*)keyboard_watch,NULL); pthread_create(&keys_thread,NULL,(void*)keyboard_watch,NULL);
if(have_nic==1) if(have_nic==1)
@@ -1813,6 +1924,7 @@ int main(int argc, char *argv[]){
int cycle_old = cycle; int cycle_old = cycle;
while(1) { while(1) {
calc_info_cycle(); calc_info_cycle();
summary_gpu = cycle == SCREEN_SUMMARY && gpu_count;
switch (cycle) { switch (cycle) {
case SCREEN_SUMMARY: case SCREEN_SUMMARY:
case SCREEN_CPU: case SCREEN_CPU:
@@ -1919,6 +2031,8 @@ int main(int argc, char *argv[]){
break; break;
} }
cycle_old = cycle; cycle_old = cycle;
if (summary_gpu) draw_gpu_column(canvas);
summary_gpu = 0;
print_info_label(canvas, tmpstr); print_info_label(canvas, tmpstr);
canvas->mode_xor = 0; canvas->mode_xor = 0;
@@ -1926,6 +2040,7 @@ int main(int argc, char *argv[]){
g15_send(g15screen_fd,(char *)canvas->buffer,G15_BUFFER_LEN); g15_send(g15screen_fd,(char *)canvas->buffer,G15_BUFFER_LEN);
g15stats_wait(wait_seconds); g15stats_wait(wait_seconds);
} }
gpu_stop();
glibtop_close(); glibtop_close();
close(g15screen_fd); close(g15screen_fd);
+443
View File
@@ -0,0 +1,443 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Read-only Linux GPU telemetry. Included privately by g15stats.c.
*/
#ifndef G15STATS_GPU_H
#define G15STATS_GPU_H
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <glob.h>
#include <limits.h>
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <stdatomic.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
typedef struct {
long long busy, used, total, temp; /* percent, bytes, bytes, Celsius */
} gpu_sample;
static const gpu_sample gpu_unknown = {-1, -1, -1, LLONG_MIN};
#define GPU_MAX 64
typedef struct {
int backend; /* 0 missing, 1 amdgpu, 2 nvidia */
char device[PATH_MAX], bus[32];
} gpu_adapter;
typedef struct {
size_t count;
gpu_sample samples[GPU_MAX];
} gpu_frame;
static gpu_adapter gpu_devices[GPU_MAX]; /* immutable while workers run */
static size_t gpu_count;
static gpu_sample gpu_cache[GPU_MAX];
static long long gpu_stamp[GPU_MAX];
static pthread_mutex_t gpu_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_t gpu_threads[GPU_MAX];
static size_t gpu_thread_count;
static atomic_int gpu_stopping;
static pid_t gpu_pending = -1; /* NVIDIA worker owns until joined. */
static long long gpu_epoch;
static size_t gpu_page;
static long long gpu_page_due;
static int gpu_selection_valid(const char *selection)
{
size_t i;
if (!strcmp(selection, "auto") || !strcmp(selection, "off")) return 1;
if (strlen(selection) != 12) return 0;
for (i = 0; i < 12; i++) {
if (i == 4 || i == 7) { if (selection[i] != ':') return 0; }
else if (i == 10) { if (selection[i] != '.') return 0; }
else if (!isxdigit((unsigned char)selection[i])) return 0;
}
return selection[8] <= '1' && selection[11] <= '7';
}
static long long gpu_milliseconds(void)
{
struct timespec now;
if (clock_gettime(CLOCK_MONOTONIC, &now))
return 0;
return (long long)now.tv_sec * 1000 + now.tv_nsec / 1000000;
}
/* Reject partial values, overflow and unsupported textual fields. */
static long long gpu_number(const char *text, long long min, long long max,
long long missing)
{
char *end;
long long value;
while (isspace((unsigned char)*text)) text++;
if (!isdigit((unsigned char)*text) && *text != '-') return missing;
errno = 0;
value = strtoll(text, &end, 10);
if (end == text || errno) return missing;
while (isspace((unsigned char)*end)) end++;
return *end || value < min || value > max ? missing : value;
}
static long long gpu_file(const char *path, long long min, long long max,
long long missing)
{
char text[96];
FILE *file = fopen(path, "re");
long long value = missing;
if (!file) return missing;
if (fgets(text, sizeof(text), file) && !ferror(file)) {
/* sysfs values fit in one short line; do not accept truncated input. */
if (fgetc(file) == EOF && !ferror(file))
value = gpu_number(text, min, max, missing);
}
fclose(file);
return value;
}
/* Select once, in sorted PCI-address order, not unstable DRM card order.
* Explicit PCI selection keeps an N/A column even for missing devices.
*/
static int gpu_detect(const char *selection)
{
glob_t paths = {0};
size_t i;
gpu_count = 0;
gpu_epoch = gpu_milliseconds();
gpu_page = 0;
gpu_page_due = gpu_epoch + 5000;
memset(gpu_stamp, 0, sizeof(gpu_stamp));
for (i = 0; i < GPU_MAX; i++) gpu_cache[i] = gpu_unknown;
if (!strcmp(selection, "off")) return 0;
int error = glob("/sys/bus/pci/devices/*", 0, NULL, &paths);
if (error && error != GLOB_NOMATCH) {
globfree(&paths);
return -1;
}
for (i = 0; i < paths.gl_pathc; i++) {
char path[PATH_MAX], driver[PATH_MAX];
const char *bus = strrchr(paths.gl_pathv[i], '/') + 1;
ssize_t len;
int backend;
if (strcmp(selection, "auto") && strcasecmp(selection, bus)) continue;
snprintf(path, sizeof(path), "%s/driver", paths.gl_pathv[i]);
len = readlink(path, driver, sizeof(driver) - 1);
if (len < 0) continue;
driver[len] = 0;
if (!strcmp(strrchr(driver, '/') + 1, "amdgpu"))
backend = 1;
else if (!strcmp(strrchr(driver, '/') + 1, "nvidia"))
backend = 2;
else continue;
if (gpu_count == GPU_MAX) {
globfree(&paths);
gpu_count = 0;
return -1; /* Never silently truncate the inventory. */
}
gpu_devices[gpu_count].backend = backend;
snprintf(gpu_devices[gpu_count].device, PATH_MAX, "%s", paths.gl_pathv[i]);
snprintf(gpu_devices[gpu_count].bus, 32, "%s", bus);
gpu_count++;
}
globfree(&paths);
if (!gpu_count && strcmp(selection, "auto")) {
memset(&gpu_devices[0], 0, sizeof(gpu_devices[0]));
snprintf(gpu_devices[0].bus, 32, "%s", selection);
gpu_count = 1;
}
return 0;
}
static gpu_sample gpu_amd(const gpu_adapter *adapter)
{
gpu_sample sample = gpu_unknown;
char path[PATH_MAX + 64];
glob_t paths = {0};
snprintf(path, sizeof(path), "%s/gpu_busy_percent", adapter->device);
sample.busy = gpu_file(path, 0, 100, -1);
snprintf(path, sizeof(path), "%s/mem_info_vram_used", adapter->device);
sample.used = gpu_file(path, 0, LLONG_MAX, -1);
snprintf(path, sizeof(path), "%s/mem_info_vram_total", adapter->device);
sample.total = gpu_file(path, 1, LLONG_MAX, -1);
snprintf(path, sizeof(path), "%s/hwmon/hwmon*/temp1_input", adapter->device);
if (!glob(path, 0, NULL, &paths) && paths.gl_pathc) {
long long temp = gpu_file(paths.gl_pathv[0], -273000, 1000000, LLONG_MIN);
if (temp != LLONG_MIN) sample.temp = temp / 1000;
}
globfree(&paths);
if (sample.total >= 0 && sample.used > sample.total) sample.used = -1;
return sample;
}
static gpu_sample gpu_csv(char *text)
{
gpu_sample sample = gpu_unknown;
char *fields[4], *cursor = text;
int i;
for (i = 0; i < 4; i++) {
fields[i] = strsep(&cursor, ",");
if (!fields[i]) return sample;
}
if (cursor) return sample;
sample.busy = gpu_number(fields[0], 0, 100, -1);
sample.used = gpu_number(fields[1], 0, LLONG_MAX / 1048576, -1);
sample.total = gpu_number(fields[2], 1, LLONG_MAX / 1048576, -1);
sample.temp = gpu_number(fields[3], -273, 1000, LLONG_MIN);
if (sample.used >= 0) sample.used *= 1048576;
if (sample.total >= 0) sample.total *= 1048576;
if (sample.total >= 0 && sample.used > sample.total) sample.used = -1;
return sample;
}
/* Only the collector calls this. Never wait for a driver in the draw thread.
* A killed but uninterruptible child remains owned, preventing process buildup.
*/
static int gpu_above_stdio(int fd)
{
int copy;
if (fd < 0 || fd >= 3) return fd;
copy = fcntl(fd, F_DUPFD_CLOEXEC, 3);
close(fd);
return copy;
}
static int gpu_command(const char *executable, char *const args[], char *text, size_t capacity)
{
int pipes[2], status, nullfd;
pid_t pid, parent = getpid(), result;
size_t used = 0;
long long deadline;
long maxfd = sysconf(_SC_OPEN_MAX);
char *const env[] = {"LC_ALL=C", NULL};
if (gpu_pending > 0) {
result = waitpid(gpu_pending, &status, WNOHANG);
if (!result || (result < 0 && errno == EINTR)) return 0;
gpu_pending = -1;
}
if (pipe2(pipes, O_CLOEXEC | O_NONBLOCK)) return 0;
pipes[0] = gpu_above_stdio(pipes[0]);
pipes[1] = gpu_above_stdio(pipes[1]);
if (pipes[0] < 0 || pipes[1] < 0) {
if (pipes[0] >= 0) close(pipes[0]);
if (pipes[1] >= 0) close(pipes[1]);
return 0;
}
/* Only the reader is nonblocking. A large all-card response must not
* make nvidia-smi fail with EAGAIN when the pipe temporarily fills. */
if (fcntl(pipes[1], F_SETFL, 0) < 0) {
close(pipes[0]);
close(pipes[1]);
return 0;
}
nullfd = gpu_above_stdio(open("/dev/null", O_RDWR | O_CLOEXEC));
if (nullfd < 0) { close(pipes[0]); close(pipes[1]); return 0; }
pid = fork();
if (!pid) {
/* Async-signal-safe child path: no stdio, allocation or shell. */
if (prctl(PR_SET_PDEATHSIG, SIGKILL) || getppid() != parent) _exit(127);
if (dup2(nullfd, 0) < 0 || dup2(pipes[1], 1) < 0 || dup2(nullfd, 2) < 0)
_exit(127);
#ifdef SYS_close_range
if (syscall(SYS_close_range, 3U, ~0U, 0) < 0)
#endif
{
long fd;
if (maxfd < 0) _exit(127);
for (fd = 3; fd < maxfd; fd++) close((int)fd);
}
execve(executable, args, env);
_exit(127);
}
close(nullfd);
close(pipes[1]);
if (pid < 0) { close(pipes[0]); return 0; }
deadline = gpu_milliseconds() + 750;
for (;;) {
ssize_t n = read(pipes[0], text + used, capacity - 1 - used);
if (n > 0) used += (size_t)n;
result = waitpid(pid, &status, WNOHANG);
if (result == pid) {
/* Drain the final bytes after exit before parsing. */
while (used < capacity - 1 &&
(n = read(pipes[0], text + used, capacity - 1 - used)) > 0)
used += (size_t)n;
close(pipes[0]);
text[used] = 0;
return used < capacity - 1 && WIFEXITED(status) && !WEXITSTATUS(status);
}
if (result < 0 && errno != EINTR) { close(pipes[0]); return 0; }
if (atomic_load(&gpu_stopping) || gpu_milliseconds() >= deadline || used == capacity - 1) break;
/* Avoid spinning on POLLHUP if stdout closes before the process exits. */
poll(NULL, 0, 20);
}
kill(pid, SIGKILL);
gpu_pending = pid;
close(pipes[0]);
return 0;
}
/* Match numeric PCI identity: NVIDIA commonly prints an eight-digit domain.
* Malformed/duplicate rows must never contaminate another adapter's sample. */
static int gpu_bus_equal(const char *a, const char *b)
{
unsigned ad, ab, as, af, bd, bb, bs, bf;
int n = 0, m = 0;
if (sscanf(a, "%8x:%2x:%2x.%1x %n", &ad, &ab, &as, &af, &n) != 4 || a[n]) return 0;
if (sscanf(b, "%8x:%2x:%2x.%1x %n", &bd, &bb, &bs, &bf, &m) != 4 || b[m]) return 0;
return ad == bd && ab == bb && as == bs && af == bf;
}
static void gpu_nvidia_parse(char *text, gpu_frame *frame)
{
char *line;
unsigned char seen[GPU_MAX] = {0};
size_t i;
while ((line = strsep(&text, "\n"))) {
char *bus = strsep(&line, ",");
if (!line) continue;
for (i = 0; i < gpu_count; i++) {
if (gpu_devices[i].backend != 2 || !gpu_bus_equal(bus, gpu_devices[i].bus)) continue;
frame->samples[i] = seen[i] ? gpu_unknown : gpu_csv(line);
seen[i] = 1;
break;
}
}
}
static void gpu_nvidia(gpu_frame *frame)
{
char text[GPU_MAX * 256];
char *const args[] = {"nvidia-smi",
"--query-gpu=pci.bus_id,utilization.gpu,memory.used,memory.total,temperature.gpu",
"--format=csv,noheader,nounits", NULL};
if (gpu_command("/usr/bin/nvidia-smi", args, text, sizeof(text)))
gpu_nvidia_parse(text, frame);
}
static void gpu_publish(size_t i, gpu_sample sample)
{
pthread_mutex_lock(&gpu_lock);
gpu_cache[i] = sample;
gpu_stamp[i] = gpu_milliseconds();
pthread_mutex_unlock(&gpu_lock);
}
/* Each AMD adapter has an independent worker; one NVIDIA worker queries all
* NVIDIA cards within one deadline. A stalled sysfs driver cannot stall peers. */
static void *gpu_watch(void *arg)
{
size_t index = (gpu_adapter *)arg - gpu_devices, i;
while (!atomic_load(&gpu_stopping)) {
if (gpu_devices[index].backend == 1) {
gpu_publish(index, gpu_amd(&gpu_devices[index]));
} else {
gpu_frame frame = {.count = gpu_count};
for (i = 0; i < gpu_count; i++) frame.samples[i] = gpu_unknown;
gpu_nvidia(&frame);
pthread_mutex_lock(&gpu_lock);
for (i = 0; i < gpu_count; i++) if (gpu_devices[i].backend == 2) {
gpu_cache[i] = frame.samples[i];
gpu_stamp[i] = gpu_milliseconds();
}
pthread_mutex_unlock(&gpu_lock);
}
for (i = 0; i < 100 && !atomic_load(&gpu_stopping); i++) poll(NULL, 0, 20);
}
return NULL;
}
static int gpu_start(void)
{
size_t i;
int nvidia = 0, failed = 0;
atomic_store(&gpu_stopping, 0);
for (i = 0; i < gpu_count; i++) {
if (!gpu_devices[i].backend || (gpu_devices[i].backend == 2 && nvidia++)) continue;
if (pthread_create(&gpu_threads[gpu_thread_count], NULL, gpu_watch, &gpu_devices[i])) failed = 1;
else gpu_thread_count++;
}
return failed;
}
/* One shared shutdown deadline, not one per adapter. A stuck kernel read may
* outlive this call; its static storage remains valid until process exit.
* Never redetect/restart after a failed stop. */
static int gpu_stop(void)
{
struct timespec deadline;
long long end = gpu_milliseconds() + 1000;
size_t i;
int failed = 0;
atomic_store(&gpu_stopping, 1);
clock_gettime(CLOCK_REALTIME, &deadline);
deadline.tv_sec++;
for (i = 0; i < gpu_thread_count; i++) {
if (pthread_timedjoin_np(gpu_threads[i], NULL, &deadline)) {
pthread_detach(gpu_threads[i]);
failed = 1;
}
}
gpu_thread_count = 0;
if (!failed && gpu_pending > 0) {
int status;
pid_t result;
do {
result = waitpid(gpu_pending, &status, WNOHANG);
if (result == gpu_pending || (result < 0 && errno == ECHILD)) {
gpu_pending = -1;
break;
}
poll(NULL, 0, 5);
} while (gpu_milliseconds() < end);
if (gpu_pending > 0) failed = 1;
}
return failed;
}
static gpu_frame gpu_snapshot(void)
{
gpu_frame frame = {.count = gpu_count};
size_t i;
pthread_mutex_lock(&gpu_lock);
for (i = 0; i < gpu_count; i++)
frame.samples[i] = !gpu_stamp[i] || gpu_milliseconds() - gpu_stamp[i] > 5000 ? gpu_unknown : gpu_cache[i];
pthread_mutex_unlock(&gpu_lock);
return frame;
}
static size_t gpu_page_first(size_t count, unsigned long long elapsed)
{
return count > 2 ? (elapsed / 5000 % ((count + 1) / 2)) * 2 : 0;
}
/* Main thread only. Advance at most one pair per actual draw, so -r 10 (or
* leaving the summary screen) cannot alias the cycle and hide half the GPUs. */
static size_t gpu_display_first(size_t count, long long now)
{
if (count > 2 && now >= gpu_page_due) {
gpu_page = (gpu_page + 1) % ((count + 1) / 2);
gpu_page_due = now + 5000;
}
return gpu_page_first(count, gpu_page * 5000ULL);
}
/* Each line fits the 52-pixel column (13 small-font character cells).
* Separate used/total rows retain useful precision, even on large cards.
*/
static void gpu_memory(char *text, size_t size, long long bytes)
{
const char *unit = "MiB";
double value = (double)bytes / 1048576;
if (bytes < 0) { snprintf(text, size, "N/A"); return; }
if (value >= 1024) { value /= 1024; unit = "GiB"; }
if (value >= 1024) { value /= 1024; unit = "TiB"; }
if (value >= 1024) { value /= 1024; unit = "PiB"; }
snprintf(text, size, "%.1f%s", value, unit);
}
#endif
+284
View File
@@ -0,0 +1,284 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#define _GNU_SOURCE 1
#include "../gpu.h"
#include <assert.h>
#include <dirent.h>
static int descriptors(void)
{
DIR *dir = opendir("/proc/self/fd");
struct dirent *entry;
int count = 0;
assert(dir);
while ((entry = readdir(dir)))
if (entry->d_name[0] != '.') count++;
closedir(dir);
return count;
}
static void unknown(gpu_sample s)
{
assert(s.busy == -1 && s.used == -1 && s.total == -1 && s.temp == LLONG_MIN);
}
static gpu_sample test_command(const char *path, char *const args[])
{
char text[256];
return gpu_command(path, args, text, sizeof(text)) ? gpu_csv(text) : gpu_unknown;
}
static void *cache_writer(void *arg)
{
size_t index = *(size_t *)arg;
int i;
for (i = 0; i < 20000; i++)
gpu_publish(index, (gpu_sample){i % 101, i, i + 1, (long long)index});
return NULL;
}
static void cache_concurrency(void)
{
pthread_t writers[2];
size_t indices[2] = {0, 1}, i;
int repeat;
gpu_count = 2;
for (i = 0; i < 2; i++) {
gpu_publish(i, (gpu_sample){0, 0, 1, (long long)i});
assert(!pthread_create(&writers[i], NULL, cache_writer, &indices[i]));
}
for (repeat = 0; repeat < 20000; repeat++) {
gpu_frame frame = gpu_snapshot();
assert(frame.count == 2);
for (i = 0; i < 2; i++) {
assert(frame.samples[i].total == frame.samples[i].used + 1);
assert(frame.samples[i].busy == frame.samples[i].used % 101);
assert(frame.samples[i].temp == (long long)i);
}
}
for (i = 0; i < 2; i++) assert(!pthread_join(writers[i], NULL));
}
static void parent_death(void)
{
pid_t child, grandchild = 0, result;
long long deadline;
char path[96];
int status;
assert(!prctl(PR_SET_CHILD_SUBREAPER, 1));
child = fork();
assert(child >= 0);
if (!child) {
char *const args[] = {"sleep", "10", NULL};
test_command("/usr/bin/sleep", args);
_exit(0);
}
snprintf(path, sizeof(path), "/proc/%ld/task/%ld/children", (long)child, (long)child);
deadline = gpu_milliseconds() + 500;
do {
FILE *file = fopen(path, "r");
long id;
if (file) {
if (fscanf(file, "%ld", &id) == 1) grandchild = (pid_t)id;
fclose(file);
}
if (!grandchild) poll(NULL, 0, 5);
} while (!grandchild && gpu_milliseconds() < deadline);
assert(grandchild > 0);
assert(!kill(child, SIGKILL));
assert(waitpid(child, &status, 0) == child);
deadline = gpu_milliseconds() + 1000;
do {
result = waitpid(grandchild, &status, WNOHANG);
if (!result) poll(NULL, 0, 5);
} while (!result && gpu_milliseconds() < deadline);
assert(result == grandchild && WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL);
assert(!prctl(PR_SET_CHILD_SUBREAPER, 0));
}
int main(int argc, char **argv)
{
gpu_sample s;
char text[256], memory[32];
char *const sleeper[] = {"sleep", "10", NULL};
char *const failure[] = {"false", NULL};
long long start;
int status;
pid_t result;
int fd_count, repeat;
assert(gpu_page_first(0, 5000) == 0);
assert(gpu_page_first(2, 5000) == 0);
assert(gpu_page_first(3, 5000) == 2);
assert(gpu_page_first(3, 10000) == 0);
for (repeat = 0; repeat < GPU_MAX; repeat += 2)
assert(gpu_page_first(GPU_MAX, (unsigned long long)repeat / 2 * 5000) == (size_t)repeat);
gpu_page = 0;
gpu_page_due = 5000;
assert(gpu_display_first(5, 0) == 0);
assert(gpu_display_first(5, 10000) == 2);
assert(gpu_display_first(5, 20000) == 4);
assert(gpu_display_first(5, 30000) == 0);
assert(gpu_display_first(5, 30001) == 0);
assert(gpu_selection_valid("auto") && gpu_selection_valid("off"));
assert(gpu_selection_valid("0000:AB:1f.7"));
assert(!gpu_selection_valid("0000:01:20.0"));
assert(!gpu_selection_valid("0000:01:00.8"));
assert(!gpu_selection_valid("-001:01:00.0"));
assert(!gpu_selection_valid("../../passwd"));
assert(!gpu_selection_valid("0000:01:00.0;id"));
assert(gpu_number(" 0\n", 0, 100, -1) == 0);
assert(gpu_number("100", 0, 100, -1) == 100);
assert(gpu_number("101", 0, 100, -1) == -1);
assert(gpu_number("12oops", 0, 100, -1) == -1);
assert(gpu_number("", 0, 100, -1) == -1);
assert(gpu_number("N/A", 0, 100, -1) == -1);
assert(gpu_number("999999999999999999999999", 0, LLONG_MAX, -1) == -1);
strcpy(text, "100, 12288, 16384, -5\n");
s = gpu_csv(text);
assert(s.busy == 100 && s.used == 12884901888LL &&
s.total == 17179869184LL && s.temp == -5);
strcpy(text, "N/A, [Not Supported], 16384, 0\n");
s = gpu_csv(text);
assert(s.busy == -1 && s.used == -1 && s.total == 17179869184LL && s.temp == 0);
strcpy(text, "0, 0, 0, N/A\n");
s = gpu_csv(text);
assert(s.busy == 0 && s.used == 0 && s.total == -1 && s.temp == LLONG_MIN);
strcpy(text, "0, 20, 10, 35\n");
assert(gpu_csv(text).used == -1);
strcpy(text, "0, 1, 2"); unknown(gpu_csv(text));
strcpy(text, "0, 1, 2, 3, 4"); unknown(gpu_csv(text));
strcpy(text, "N/A, N/A, N/A, N/A"); unknown(gpu_csv(text));
gpu_memory(memory, sizeof(memory), -1); assert(!strcmp(memory, "N/A"));
gpu_memory(memory, sizeof(memory), 19 * 1048576); assert(!strcmp(memory, "19.0MiB"));
gpu_memory(memory, sizeof(memory), 17179869184LL); assert(!strcmp(memory, "16.0GiB"));
gpu_memory(memory, sizeof(memory), LLONG_MAX); assert(strlen(memory) <= 11);
assert(!gpu_detect("off") && !gpu_count);
assert(!gpu_detect("ffff:ff:1f.7") && gpu_count == 1 && !gpu_devices[0].backend);
gpu_cache[0] = s;
gpu_stamp[0] = gpu_milliseconds() - 6000;
unknown(gpu_snapshot().samples[0]);
{
gpu_frame frame = {.count = 3};
gpu_count = 3;
for (repeat = 0; repeat < 3; repeat++) {
snprintf(gpu_devices[repeat].bus, 32, "0000:%02x:00.0", repeat + 1);
gpu_devices[repeat].backend = repeat == 1 ? 1 : 2;
frame.samples[repeat] = gpu_unknown;
}
frame.samples[1] = (gpu_sample){77, 1, 2, 42};
strcpy(text, "00000000:03:00.0, 90, 4, 8, 55\n00000000:01:00.0, N/A, 1, 2, N/A\n");
gpu_nvidia_parse(text, &frame);
assert(frame.samples[0].busy == -1 && frame.samples[0].used == 1048576);
assert(frame.samples[1].busy == 77 && frame.samples[1].used == 1);
assert(frame.samples[2].busy == 90 && frame.samples[2].temp == 55);
strcpy(text, "00000000:03:00.0, 1, 4, 8, 55\n0000:03:00.0, 2, 4, 8, 55\n");
gpu_nvidia_parse(text, &frame);
unknown(frame.samples[2]);
assert(!gpu_bus_equal("0000:01:00.0junk", "0000:01:00.0"));
assert(gpu_bus_equal("00000000:AB:00.0 ", "0000:ab:00.0"));
assert(!gpu_bus_equal("0001:01:00.0", "0000:01:00.0"));
for (repeat = 0; repeat < 3; repeat++) gpu_publish(repeat, frame.samples[repeat]);
gpu_stamp[0] = gpu_milliseconds() - 6000;
frame = gpu_snapshot();
unknown(frame.samples[0]);
assert(frame.samples[1].busy == 77);
unknown(frame.samples[2]);
/* Missing AMD path is independent of the populated neighboring cache. */
snprintf(gpu_devices[0].device, PATH_MAX, "/nonexistent/g15stats-test");
gpu_publish(0, gpu_amd(&gpu_devices[0]));
assert(gpu_snapshot().samples[1].busy == 77);
}
/* Real processes exercise the deadline and reaping, not fake GPU files. */
start = gpu_milliseconds();
unknown(test_command("/usr/bin/sleep", sleeper));
assert(gpu_milliseconds() - start >= 750);
assert(gpu_milliseconds() - start < 1500);
poll(NULL, 0, 50);
unknown(test_command("/usr/bin/false", failure));
result = waitpid(-1, &status, WNOHANG);
assert(result == -1 && errno == ECHILD);
unknown(test_command("/nonexistent/g15stats-test", failure));
fd_count = descriptors();
for (repeat = 0; repeat < 20; repeat++)
unknown(test_command("/usr/bin/false", failure));
assert(descriptors() == fd_count);
{
char payload[12001], output[GPU_MAX * 256];
char *const args[] = {"printf", "%s", payload, NULL};
memset(payload, 'x', sizeof(payload) - 1);
payload[sizeof(payload) - 1] = 0;
assert(gpu_command("/usr/bin/printf", args, output, sizeof(output)));
assert(!strcmp(payload, output));
assert(!gpu_command("/usr/bin/printf", args, output, 256));
poll(NULL, 0, 50);
unknown(test_command("/usr/bin/false", failure));
}
parent_death();
cache_concurrency();
if (argc == 2) {
gpu_frame frame;
size_t i;
assert(!gpu_detect(argv[1]) && gpu_count);
assert(!gpu_start());
start = gpu_milliseconds();
do {
frame = gpu_snapshot();
for (i = 0; i < frame.count; i++) if (frame.samples[i].total <= 0) break;
if (i == frame.count) break;
poll(NULL, 0, 10);
} while (gpu_milliseconds() - start < 1800);
for (i = 0; i < frame.count; i++) {
char path[PATH_MAX + 32];
s = frame.samples[i];
if (i) assert(strcmp(gpu_devices[i-1].bus, gpu_devices[i].bus) < 0);
snprintf(path, sizeof(path), "%s/vendor", gpu_devices[i].device);
FILE *vendor = fopen(path, "r");
unsigned id;
assert(vendor && fscanf(vendor, "%x", &id) == 1);
fclose(vendor);
assert(id == (gpu_devices[i].backend == 1 ? 0x1002U : 0x10deU));
printf("LIVE %zu %s vendor=%04x backend=%d utilization=%lld used=%lld total=%lld temp=%lld\n",
i+1, gpu_devices[i].bus, id, gpu_devices[i].backend, s.busy, s.used, s.total, s.temp);
assert(s.busy >= 0 && s.used >= 0 && s.total > 0 && s.temp != LLONG_MIN);
}
for (repeat = 0; repeat < 100000; repeat++) {
frame = gpu_snapshot();
assert(frame.count == gpu_count);
for (i = 0; i < frame.count; i++) assert(frame.samples[i].total > 0);
}
start = gpu_milliseconds();
assert(!gpu_stop());
assert(gpu_milliseconds() - start < 1200);
puts("PASS all live adapters, sysfs vendor identities, concurrent snapshots and bounded shutdown");
/* Read-only failure injection: point just the stopped AMD collector at
* a nonexistent path, then prove the actual NVIDIA peer remains live. */
if (gpu_count > 1) {
size_t amd = GPU_MAX, nv = GPU_MAX;
char saved[PATH_MAX];
for (i = 0; i < gpu_count; i++) {
if (gpu_devices[i].backend == 1) amd = i;
if (gpu_devices[i].backend == 2) nv = i;
}
if (amd < GPU_MAX && nv < GPU_MAX) {
strcpy(saved, gpu_devices[amd].device);
strcpy(gpu_devices[amd].device, "/nonexistent/g15stats-test");
for (i = 0; i < gpu_count; i++) gpu_publish(i, gpu_unknown);
assert(!gpu_start());
start = gpu_milliseconds();
do {
frame = gpu_snapshot();
if (frame.samples[nv].total > 0) break;
poll(NULL, 0, 10);
} while (gpu_milliseconds() - start < 1800);
assert(frame.samples[nv].total > 0 && frame.samples[nv].busy >= 0);
unknown(frame.samples[amd]);
assert(!gpu_stop());
strcpy(gpu_devices[amd].device, saved);
puts("PASS failed AMD collector isolated from real live NVIDIA peer");
}
}
}
puts("PASS GPU parsing, units, missing data, expiry, selection, deadline, reap and parent death");
return 0;
}
+53
View File
@@ -0,0 +1,53 @@
#!/bin/sh
# Convert existing render-test exports without trimming, resampling or labels
# inside the framebuffer. ImageMagick is optional, not a make check dependency.
set -eu
if [ "$#" -ne 1 ]; then
echo "Usage: sh tests/render-images.sh ABSOLUTE_OUTPUT_DIRECTORY" >&2
exit 2
fi
cd "$1"
for pgm in ./*-[01].pgm; do
[ -f "$pgm" ] || continue
base=${pgm%.pgm}
[ "$(wc -c < "$base.bin")" -eq 860 ]
{ printf 'P4\n160 43\n'; cat "$base.bin"; } > "$base.pbm"
# Compare the packed 1-bit buffer independently with the PGM export.
compare -metric AE "$base.pbm" "$pgm" null: 2> "$base-pixels.log"
convert "$pgm" +repage -type TrueColor "PNG24:$base-native.png"
convert "$base-native.png" -sample 800% +repage "PNG24:$base-8x.png"
convert "$base-8x.png" -sample 160x43! +repage "PNG24:$base-roundtrip.png"
compare -metric AE "$pgm" "$base-native.png" null: 2>> "$base-pixels.log"
compare -metric AE "$pgm" "$base-roundtrip.png" null: 2>> "$base-pixels.log"
rm "$base-roundtrip.png"
# Smaller contact tiles avoid shrinking wide sheets in image viewers.
# Keep the eightfold originals available for individual pixel inspection.
convert "$base-native.png" -sample 400% +repage "PNG24:$base-4x.png"
done
for group in off nvidia amd single dual large unknown odd maximum; do
[ -f "$group-0-4x.png" ] || continue
montage -font DejaVu-Sans -pointsize 14 -background '#dddddd' \
-label "$group: five-row summary (4x)" "$group-0-4x.png" \
-label "$group: four-row summary (4x)" "$group-1-4x.png" \
-tile 1x2 -geometry +8+8 "PNG24:contact-$group.png"
height=$(identify -format '%h' "contact-$group.png")
for variant in 0 1; do
top=$((8 + variant * height / 2))
convert "contact-$group.png" -crop "640x172+8+$top" +repage \
"PNG24:$group-tile-check.png"
compare -metric AE "$group-$variant-4x.png" "$group-tile-check.png" \
null: 2>> "$group-contact-pixels.log"
done
rm "$group-tile-check.png"
done
if [ -f contact-off.png ] && [ -f contact-nvidia.png ] && [ -f contact-amd.png ]; then
convert contact-off.png contact-nvidia.png contact-amd.png -append +repage \
PNG24:contact-legacy.png
fi
if [ -f contact-dual.png ] && [ -f contact-large.png ] && [ -f contact-unknown.png ]; then
convert contact-dual.png contact-large.png contact-unknown.png -append +repage \
PNG24:contact-metrics.png
fi
if [ -f contact-odd.png ] && [ -f contact-maximum.png ]; then
convert contact-odd.png contact-maximum.png -append +repage PNG24:contact-paging.png
fi
+13
View File
@@ -0,0 +1,13 @@
#!/bin/sh
# Native-pixel checks only: no image conversion, daemon or input-device access.
set -eu
renderer=${RENDER_TEST:-./render-test}
out=$(mktemp -d "${TMPDIR:-/tmp}/g15stats-layouts-XXXXXX")
trap 'rm -rf "$out"' EXIT HUP INT TERM
"$renderer" off "$out/off"
"$renderer" off "$out/single" 1
"$renderer" off "$out/dual" 2
"$renderer" off "$out/large" 2 0 large
"$renderer" off "$out/unknown" 2 0 unknown
"$renderer" off "$out/odd" 3 1
"$renderer" off "$out/maximum" 64 31
+230
View File
@@ -0,0 +1,230 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Exercise the real renderer without any daemon connection or input thread.
*/
#define main g15stats_application_main
#include "../g15stats.c"
#undef main
#include <assert.h>
/* Independent font-bitmap oracle: do not repeat renderString on a second
* canvas, since that also repeats any clipping/erasure in the renderer.
* The default fonts have zero gap and one blank trailing pixel column.
* Compare every cell, including blank pixels, against the font file's bits. */
static void assert_text(g15canvas *canvas, const char *text, int size, int x, int y, int right)
{
g15font *font = g15r_requestG15DefaultFont(size);
assert(font && font->default_gap == 0);
assert(font->font_height == font->ascender_height);
for (; *text; text++) {
unsigned char c = (unsigned char)*text;
int width = font->glyph[c].width;
int stride = (width + 7) / 8;
assert(font->active[c] && font->glyph[c].buffer);
assert(x >= 0 && x + width <= 160);
assert(y >= 0 && y + font->font_height <= 43);
for (int dy = 0; dy < font->font_height; dy++)
for (int dx = 0; dx < width; dx++) {
int bit = !!(font->glyph[c].buffer[dy * stride + dx / 8] &
(0x80 >> (dx % 8)));
/* A 13-character first-column header's final blank advance
* pixel shares x=105 with the next divider; no ink may do so. */
if (x + dx >= right) {
assert(!text[1] && dx == width - 1 && !bit);
continue;
}
if (g15r_getPixel(canvas, x + dx, y + dy) != bit) {
fprintf(stderr, "Glyph %c pixel (%d,%d): expected %d, got %d\n",
c, x + dx, y + dy, bit,
g15r_getPixel(canvas, x + dx, y + dy));
assert(0);
}
}
x += width;
}
}
int main(int argc, char **argv)
{
g15canvas canvas, before;
char tmp[MAX_LINES];
int x, y, variant;
size_t i;
g15font *font = g15r_requestG15DefaultFont(G15_TEXT_SMALL);
assert(font);
assert(g15r_testG15FontWidth(font, "T 8192.0PiB") <= 52);
assert(g15r_testG15FontWidth(font, "UTL 100%") <= 52);
{
char lines[5][32];
compact_ram_lines(lines, LLONG_MAX, LLONG_MAX);
assert(strstr(lines[1], "PiB") && strstr(lines[2], "PiB"));
assert(g15r_testG15FontWidth(font, lines[1]) <= 50);
assert(g15r_testG15FontWidth(font, lines[2]) <= 50);
compact_ram_lines(lines, 19 * 1048576, 17179869184LL);
assert(!strcmp(lines[1], "R 19.0MiB"));
assert(!strcmp(lines[2], "T 16.0GiB"));
}
memset(&canvas, 0xa5, sizeof(canvas));
init_canvas(&canvas);
assert(!canvas.mode_xor && !canvas.mode_reverse && !canvas.mode_cache);
for (y = 0; y < 43; y++) for (x = 0; x < 160; x++)
assert(!g15r_getPixel(&canvas, x, y));
/* Original full-width geometry must remain exact with the column off. */
init_canvas(&before);
summary_gpu = 0;
drawBar_both(&canvas, 1, 8, 37, 100, 63, 100);
g15r_drawBar(&before, BAR_START, 1, BAR_END, 8, G15_COLOR_BLACK, 37, 100, 4);
drawBar_reversed(&before, BAR_START, 1, BAR_END, 8, G15_COLOR_BLACK, 63, 100, 5);
assert(!memcmp(canvas.buffer, before.buffer, sizeof(canvas.buffer)));
glibtop_init();
/* No device fixtures: sample the actual selected GPU when requested. */
assert(!gpu_detect(argc > 1 ? argv[1] : "off"));
if (gpu_count && gpu_devices[0].backend) {
long long start = gpu_milliseconds();
assert(!gpu_start());
do {
gpu_frame frame = gpu_snapshot();
for (i = 0; i < frame.count; i++) if (frame.samples[i].total < 0) break;
if (i == frame.count) break;
poll(NULL, 0, 10);
} while (gpu_milliseconds() - start < 1800);
assert(i == gpu_count);
assert(!gpu_stop());
}
/* No-argument make check retains the worst-width fixture. Explicit off
* must exercise the application's actual zero-GPU/full-width layout.
* Optional count/page/profile arguments are in-memory visual cases only. */
if (!gpu_count && (argc == 1 || argc > 3)) {
gpu_count = argc > 3 ? (size_t)atoi(argv[3]) : 2;
assert(gpu_count >= 1 && gpu_count <= GPU_MAX);
gpu_devices[0].backend = 2;
gpu_devices[1].backend = 1;
strcpy(gpu_devices[0].bus, "0000:01:00.0");
strcpy(gpu_devices[1].bus, "0000:11:00.0");
gpu_publish(0, (gpu_sample){100, LLONG_MAX, LLONG_MAX, -273});
gpu_publish(1, (gpu_sample){100, LLONG_MAX, LLONG_MAX, 1000});
for (i = 2; i < gpu_count; i++) {
gpu_devices[i].backend = 1;
snprintf(gpu_devices[i].bus, 32, "0001:%02x:1f.7", (unsigned)i);
gpu_publish(i, i % 2 ? gpu_unknown : (gpu_sample){100, LLONG_MAX, LLONG_MAX, -273});
}
if (gpu_count > 2) {
/* Exercise every pair at the maximum inventory, including the
* 13-character AMD64 header and an odd final card. */
for (i = 0; i < (gpu_count + 1) / 2; i++) {
gpu_page = i;
gpu_page_due = LLONG_MAX;
g15r_clearScreen(&canvas, G15_COLOR_WHITE);
draw_gpu_column(&canvas);
for (y = 35; y < 43; y++) for (x = 0; x < 160; x++)
assert(!g15r_getPixel(&canvas, x, y));
}
/* Keep the final pair for the exported preview and pixel checks. */
}
}
if (argc > 4) {
gpu_page = (size_t)atoi(argv[4]);
assert(gpu_page < (gpu_count + 1) / 2);
gpu_page_due = LLONG_MAX;
}
if (argc > 5) {
assert(!strcmp(argv[5], "large") || !strcmp(argv[5], "unknown"));
for (i = 0; i < gpu_count; i++)
gpu_publish(i, !strcmp(argv[5], "unknown") ? gpu_unknown :
(gpu_sample){100, 100LL << 30, 192LL << 30, 1000});
}
for (i = 0; i < gpu_count; i++) {
char lines[5][32];
int row;
gpu_lines(i, gpu_cache[i], lines);
for (row = 0; row < 5; row++) {
assert(g15r_testG15FontWidth(font, lines[row]) <= 52);
printf("GPU %zu row %d: %s\n", i + 1, row, lines[row]);
}
}
for (variant = 0; variant < 2; variant++) {
cycle = SCREEN_SUMMARY;
mode[SCREEN_SUMMARY] = variant;
have_temp = have_fan = 0; /* Keep legacy generic hwmon probing out of QA. */
summary_gpu = gpu_count != 0;
draw_cpu_screen_multicore(&canvas, tmp, 0);
before = canvas;
draw_gpu_column(&canvas);
for (y = 0; y < 43; y++)
for (x = 0; x < 160; x++)
if (x < (gpu_count > 1 ? 51 : 105) || y >= 35)
assert(g15r_getPixel(&canvas, x, y) == g15r_getPixel(&before, x, y));
/* Re-render each column on an empty canvas: exact pixels prove neither
* neighboring column nor CPU text overlaps it, including edge glyphs. */
{
gpu_frame frame = gpu_snapshot();
size_t first = gpu_page_first(frame.count, gpu_page * 5000ULL);
for (i = first; i < frame.count && i < first + 2; i++) {
g15canvas isolated;
char lines[5][32];
int row, left = frame.count == 1 ? 108 : 54 + (int)(i-first) * 54;
init_canvas(&isolated);
gpu_lines(i, frame.samples[i], lines);
for (row = 0; row < 5; row++)
g15r_renderString(&isolated, (unsigned char *)lines[row], 0, G15_TEXT_SMALL, left, 1+row*7);
for (y = 0; y < 35; y++) for (x = left; x < (left == 54 ? 105 : 160); x++)
assert(g15r_getPixel(&canvas, x, y) == g15r_getPixel(&isolated, x, y));
}
}
summary_gpu = 0;
print_time_info(&canvas, tmp);
assert(g15r_testG15FontWidth(font, tmp) <= 160);
assert_text(&canvas, tmp, G15_TEXT_SMALL,
80 - (int)strlen(tmp) * 4 / 2, INFO_ROW, 160);
if (gpu_count < 2) {
const char *label = "TOTAL";
for (int row = 0; row < 5; row++) {
char letter[2] = {label[row], 0};
assert_text(&canvas, letter, G15_TEXT_MED,
gpu_count ? 98 : TEXT_RIGHT, row * 7, 160);
}
}
{
gpu_frame frame = gpu_snapshot();
size_t first = gpu_page_first(frame.count, gpu_page * 5000ULL);
for (i = first; i < frame.count && i < first + 2; i++) {
char lines[5][32];
int left = frame.count == 1 ? 108 : 54 + (int)(i-first) * 54;
gpu_lines(i, frame.samples[i], lines);
for (int row = 0; row < 5; row++)
assert_text(&canvas, lines[row], G15_TEXT_SMALL, left, 1+row*7,
left == 54 ? 105 : 160);
}
}
if (argc > 2) {
char path[PATH_MAX];
snprintf(path, sizeof(path), "%s-%d.bin", argv[2], variant);
FILE *raw = fopen(path, "wb");
assert(raw);
assert(fwrite(canvas.buffer, 1, 160 * 43 / 8, raw) == 160 * 43 / 8);
assert(!fclose(raw));
snprintf(path, sizeof(path), "%s-%d.pgm", argv[2], variant);
FILE *out = fopen(path, "w");
assert(out);
fprintf(out, "P2\n160 43\n255\n");
for (y = 0; y < 43; y++) {
for (x = 0; x < 160; x++) fprintf(out, "%d ", g15r_getPixel(&canvas, x, y) ? 0 : 255);
fputc('\n', out);
}
assert(!fclose(out));
}
}
if (argc > 2) {
FILE *out = fopen(argv[2], "w");
assert(out);
fprintf(out, "P2\n160 43\n255\n");
for (y = 0; y < 43; y++) {
for (x = 0; x < 160; x++)
fprintf(out, "%d ", g15r_getPixel(&canvas, x, y) ? 0 : 255);
fputc('\n', out);
}
assert(!fclose(out));
}
glibtop_close();
puts("PASS real LCD buffer: initialization, original geometry, both summary variants, independent glyph bits, complete TOTAL/footer, column bounds");
return 0;
}
+77
View File
@@ -0,0 +1,77 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Full application startup/draw loop, real libgtop and bitmap renderer.
* Only daemon transport is replaced. No TCP, USB, or input-device access.
* Default: GPU off, three frames. Optional: runtime-test auto 21 verifies
* real read-only GPU samples over twenty seconds (not a physical LCD test).
*/
#define main g15stats_application_main
#include "../g15stats.c"
#undef main
#include <assert.h>
static unsigned frames, target = 3;
static int peer = -1;
/* Catch the ABI-unsafe initializer even with a non-TTF renderer in CI.
* Do not stub any bitmap drawing or host statistics functions. */
void g15r_initCanvas(g15canvas *canvas)
{
(void)canvas;
fputs("FAIL: bitmap client called ABI-dependent g15r_initCanvas\n", stderr);
abort();
}
int new_g15_screen(int type)
{
int pair[2];
assert(type == G15_G15RBUF);
assert(!socketpair(AF_UNIX, SOCK_STREAM, 0, pair));
peer = pair[1]; /* No key events: actual keyboard thread blocks in recv. */
return pair[0];
}
int g15_send(int fd, char *buffer, unsigned int length)
{
unsigned ink = 0;
gpu_frame frame = gpu_snapshot();
(void)fd;
assert(length == G15_BUFFER_LEN);
for (unsigned i = 0; i < 160 * 43 / 8; i++) ink += !!buffer[i];
assert(ink);
fprintf(stderr, "runtime frame %u: %u ink bytes, %zu GPUs\n",
++frames, ink, frame.count);
if (frames == target) {
glibtop_cpu cpu;
glibtop_mem mem;
glibtop_get_cpu(&cpu);
glibtop_get_mem(&mem);
assert(cpu.total > 0 && mem.total > 0 && mem.free <= mem.total);
fprintf(stderr, "host stats: CPU ticks=%llu RAM total=%llu free=%llu\n",
(unsigned long long)cpu.total, (unsigned long long)mem.total,
(unsigned long long)mem.free);
for (size_t i = 0; i < frame.count; i++) {
gpu_sample s = frame.samples[i];
fprintf(stderr, "%s: busy=%lld used=%lld total=%lld temp=%lld\n",
gpu_devices[i].bus, s.busy, s.used, s.total, s.temp);
assert(s.busy >= 0 && s.used >= 0 && s.total > 0 && s.temp != LLONG_MIN);
}
assert(!gpu_stop());
/* Full main is intentionally endless. Terminate without closing the
* peer first (which would wake the legacy keyboard loop with EOF). */
fputs("PASS full main, isolated transport, real renderer/collectors\n", stderr);
_Exit(0);
}
return length;
}
int main(int argc, char **argv)
{
char *args[] = {"g15stats", "--gpu", argc > 1 ? argv[1] : "off", NULL};
if (argc > 2) {
long n = gpu_number(argv[2], 3, 30, -1);
assert(n >= 3);
target = (unsigned)n;
}
alarm(target + 10); /* Failure bound, not a passing timeout. */
return g15stats_application_main(3, args);
}