251 lines
8.7 KiB
C
251 lines
8.7 KiB
C
// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
|
// See LICENSE.md for license information.
|
|
// SPDX-License-Identifier: MIT
|
|
//
|
|
// diffUberPerf.c - compare two UBER logs (joeylog.txt) op by op, by speed
|
|
// (ops/sec).
|
|
//
|
|
// diffUberPerf [--threshold N] <reference-log> <test-log>
|
|
//
|
|
// diffUberHashes' sibling: that one compares pixels, this one finds the
|
|
// biggest speed gaps against the reference (the IIgs, the perf floor every
|
|
// other port must match or beat), to aim asm and algorithm work at them.
|
|
//
|
|
// Prints a table of every op, ref and test ops/sec and their ratio
|
|
// (test/ref), sorted so the worst gaps come first: ops MISSING from the
|
|
// test log, then those below the threshold (FAIL), then the rest (ok), each
|
|
// by ratio and then name; ops only the test log has follow as EXTRA. The
|
|
// threshold (default 1.0, parity; --threshold N or --threshold=N, anywhere
|
|
// on the line) is the ratio an op must reach; 0.8 is the looser acceptance
|
|
// of a port's first planar pass. An op the reference ran at 0 ops/sec has
|
|
// the ratio inf when the test ran it at all, else 1. Values are those of
|
|
// each log's last run (toolUberLog.h).
|
|
//
|
|
// Exit: 0 every op at or above the threshold, 1 at least one below it or
|
|
// missing, 2 a usage error, a bad threshold, or a log that cannot be read
|
|
// or has no UBER lines.
|
|
|
|
#include <inttypes.h>
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "toolFile.h"
|
|
#include "toolUberLog.h"
|
|
|
|
#define TOOL_NAME "diffUberPerf"
|
|
#define THRESHOLD_FLAG "--threshold"
|
|
#define THRESHOLD_DEFAULT 1.0
|
|
#define LOG_COUNT 2u
|
|
#define PASS_EXIT 0
|
|
#define BELOW_EXIT 1
|
|
#define USAGE_EXIT 2
|
|
// The table's columns: the op (as wide as the widest name, at least the
|
|
// heading), then ref, test, and ratio right-aligned.
|
|
#define OP_HEADING "op"
|
|
#define NUMBER_WIDTH 10
|
|
#define RATIO_WIDTH 7
|
|
#define STATUS_RULE "------"
|
|
#define COLUMN_GAP " "
|
|
#define EXTRA_NAME "EXTRA"
|
|
// Room for an ops/sec count or a ratio (at most 2^64 to two places) as text.
|
|
#define TEXT_BYTES 32u
|
|
|
|
// A row's standing, in the order the table sorts them.
|
|
typedef enum StatusE {
|
|
STATUS_MISSING = 0,
|
|
STATUS_FAIL,
|
|
STATUS_OK
|
|
} StatusE;
|
|
|
|
// A reference op's row: the test log's op (NULL when it has none) and the
|
|
// ratio (0 for a missing op).
|
|
typedef struct RowT {
|
|
const ToolUberOpT *ref;
|
|
const ToolUberOpT *test;
|
|
double ratio;
|
|
StatusE status;
|
|
} RowT;
|
|
|
|
static const char *const gStatusNames[] = {"MISSING", "FAIL", "ok"};
|
|
|
|
static int nameCompare(const ToolUberOpT *a, const ToolUberOpT *b);
|
|
static size_t nameWidest(const ToolUberLogT *log, size_t width);
|
|
static int rowCompare(const void *a, const void *b);
|
|
static void rowPrint(const ToolUberOpT *op, const char *refText, const char *testText, const char *ratioText, const char *status, size_t opWidth);
|
|
static void rulePrint(size_t width);
|
|
static int usage(void);
|
|
|
|
|
|
// Op names in byte order, a name before the longer ones it starts.
|
|
static int nameCompare(const ToolUberOpT *a, const ToolUberOpT *b) {
|
|
int order;
|
|
|
|
order = memcmp(a->name, b->name, a->nameLen < b->nameLen ? a->nameLen : b->nameLen);
|
|
if (order != 0) {
|
|
return order;
|
|
}
|
|
return (a->nameLen > b->nameLen) - (a->nameLen < b->nameLen);
|
|
}
|
|
|
|
|
|
// The widest of width and the log's op names.
|
|
static size_t nameWidest(const ToolUberLogT *log, size_t width) {
|
|
size_t i;
|
|
|
|
for (i = 0; i < log->count; i++) {
|
|
width = log->ops[i].nameLen > width ? log->ops[i].nameLen : width;
|
|
}
|
|
return width;
|
|
}
|
|
|
|
|
|
// The table's order: by standing, then ratio, then name.
|
|
static int rowCompare(const void *a, const void *b) {
|
|
const RowT *x;
|
|
const RowT *y;
|
|
|
|
x = (const RowT *)a;
|
|
y = (const RowT *)b;
|
|
if (x->status != y->status) {
|
|
return x->status < y->status ? -1 : 1;
|
|
}
|
|
if (x->ratio != y->ratio) {
|
|
return x->ratio < y->ratio ? -1 : 1;
|
|
}
|
|
return nameCompare(x->ref, y->ref);
|
|
}
|
|
|
|
|
|
static void rowPrint(const ToolUberOpT *op, const char *refText, const char *testText, const char *ratioText, const char *status, size_t opWidth) {
|
|
toolUberNamePrint(stdout, op);
|
|
printf("%*s" COLUMN_GAP "%*s" COLUMN_GAP "%*s" COLUMN_GAP "%*s" COLUMN_GAP "%s\n", (int)(opWidth - op->nameLen), "", NUMBER_WIDTH, refText, NUMBER_WIDTH, testText, RATIO_WIDTH, ratioText, status);
|
|
}
|
|
|
|
|
|
// A column's rule: width dashes, then the gap to the next column.
|
|
static void rulePrint(size_t width) {
|
|
size_t i;
|
|
|
|
for (i = 0; i < width; i++) {
|
|
putchar('-');
|
|
}
|
|
fputs(COLUMN_GAP, stdout);
|
|
}
|
|
|
|
|
|
static int usage(void) {
|
|
fprintf(stderr, "usage: %s [%s N] <reference-log> <test-log>\n", TOOL_NAME, THRESHOLD_FLAG);
|
|
return USAGE_EXIT;
|
|
}
|
|
|
|
|
|
int main(int argc, char **argv) {
|
|
ToolUberLogT ref;
|
|
ToolUberLogT test;
|
|
RowT *rows;
|
|
const char *logs[LOG_COUNT];
|
|
const char *value;
|
|
char *end;
|
|
char refText[TEXT_BYTES];
|
|
char testText[TEXT_BYTES];
|
|
char ratioText[TEXT_BYTES];
|
|
double threshold;
|
|
size_t logCount;
|
|
size_t flagLen;
|
|
size_t opWidth;
|
|
size_t fails;
|
|
size_t i;
|
|
int a;
|
|
|
|
toolFileName(TOOL_NAME);
|
|
threshold = THRESHOLD_DEFAULT;
|
|
logCount = 0u;
|
|
flagLen = strlen(THRESHOLD_FLAG);
|
|
for (a = 1; a < argc; a++) {
|
|
if (strncmp(argv[a], THRESHOLD_FLAG, flagLen) == 0 && (argv[a][flagLen] == '\0' || argv[a][flagLen] == '=')) {
|
|
if (argv[a][flagLen] == '=') {
|
|
value = argv[a] + flagLen + 1;
|
|
} else if (a + 1 < argc) {
|
|
value = argv[++a];
|
|
} else {
|
|
return usage();
|
|
}
|
|
threshold = strtod(value, &end);
|
|
if (end == value || *end != '\0') {
|
|
fprintf(stderr, "%s: bad threshold '%s'\n", TOOL_NAME, value);
|
|
return USAGE_EXIT;
|
|
}
|
|
} else if (argv[a][0] == '-' || logCount == LOG_COUNT) {
|
|
return usage();
|
|
} else {
|
|
logs[logCount++] = argv[a];
|
|
}
|
|
}
|
|
if (logCount != LOG_COUNT) {
|
|
return usage();
|
|
}
|
|
if (!toolUberRead(TOOL_NAME, logs[0], TOOL_UBER_PERF, &ref) || !toolUberRead(TOOL_NAME, logs[1], TOOL_UBER_PERF, &test)) {
|
|
return USAGE_EXIT;
|
|
}
|
|
if (ref.count == 0u) {
|
|
fprintf(stderr, "%s: no UBER lines in %s\n", TOOL_NAME, logs[0]);
|
|
return USAGE_EXIT;
|
|
}
|
|
if (test.count == 0u) {
|
|
fprintf(stderr, "%s: no UBER lines in %s\n", TOOL_NAME, logs[1]);
|
|
return USAGE_EXIT;
|
|
}
|
|
|
|
rows = (RowT *)calloc(ref.count, sizeof(RowT));
|
|
if (rows == NULL) {
|
|
toolFail("out of memory");
|
|
}
|
|
for (i = 0; i < ref.count; i++) {
|
|
rows[i].ref = &ref.ops[i];
|
|
rows[i].test = toolUberFind(&test, ref.ops[i].name, ref.ops[i].nameLen);
|
|
if (rows[i].test == NULL) {
|
|
rows[i].status = STATUS_MISSING;
|
|
continue;
|
|
}
|
|
if (ref.ops[i].opsPerSec == 0u) {
|
|
rows[i].ratio = rows[i].test->opsPerSec > 0u ? INFINITY : 1.0;
|
|
} else {
|
|
rows[i].ratio = (double)rows[i].test->opsPerSec / (double)ref.ops[i].opsPerSec;
|
|
}
|
|
rows[i].status = rows[i].ratio >= threshold ? STATUS_OK : STATUS_FAIL;
|
|
}
|
|
qsort(rows, ref.count, sizeof(RowT), rowCompare);
|
|
|
|
opWidth = nameWidest(&test, nameWidest(&ref, strlen(OP_HEADING)));
|
|
printf("%-*s" COLUMN_GAP "%*s" COLUMN_GAP "%*s" COLUMN_GAP "%*s" COLUMN_GAP "status\n", (int)opWidth, OP_HEADING, NUMBER_WIDTH, "ref", NUMBER_WIDTH, "test", RATIO_WIDTH, "ratio");
|
|
rulePrint(opWidth);
|
|
rulePrint(NUMBER_WIDTH);
|
|
rulePrint(NUMBER_WIDTH);
|
|
rulePrint(RATIO_WIDTH);
|
|
puts(STATUS_RULE);
|
|
|
|
fails = 0u;
|
|
for (i = 0; i < ref.count; i++) {
|
|
snprintf(refText, sizeof(refText), "%" PRIu64, rows[i].ref->opsPerSec);
|
|
testText[0] = '\0';
|
|
ratioText[0] = '\0';
|
|
if (rows[i].test != NULL) {
|
|
snprintf(testText, sizeof(testText), "%" PRIu64, rows[i].test->opsPerSec);
|
|
snprintf(ratioText, sizeof(ratioText), "%.2fx", rows[i].ratio);
|
|
}
|
|
rowPrint(rows[i].ref, refText, testText, ratioText, gStatusNames[rows[i].status], opWidth);
|
|
if (rows[i].status != STATUS_OK) {
|
|
fails++;
|
|
}
|
|
}
|
|
for (i = 0; i < test.count; i++) {
|
|
if (toolUberFind(&ref, test.ops[i].name, test.ops[i].nameLen) == NULL) {
|
|
snprintf(testText, sizeof(testText), "%" PRIu64, test.ops[i].opsPerSec);
|
|
rowPrint(&test.ops[i], "", testText, "", EXTRA_NAME, opWidth);
|
|
}
|
|
}
|
|
printf("\nthreshold: %.2fx (%zu ops compared, %zu below threshold)\n", threshold, ref.count, fails);
|
|
return fails > 0u ? BELOW_EXIT : PASS_EXIT;
|
|
}
|