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.
230 lines
10 KiB
C
230 lines
10 KiB
C
/* 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;
|
|
} |