289 lines
8.3 KiB
C
289 lines
8.3 KiB
C
// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
|
// See LICENSE.md for license information.
|
|
// SPDX-License-Identifier: MIT
|
|
//
|
|
// toolUberLog.c - UBER's log for the JoeyLib host tools (toolUberLog.h).
|
|
|
|
#define _DEFAULT_SOURCE
|
|
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
|
|
#include "toolFile.h"
|
|
#include "toolUberLog.h"
|
|
|
|
#define TIMED_PREFIX "UBER:"
|
|
#define CHECK_PREFIX "UBER-CHK:"
|
|
#define CHECK_NAME "chk:"
|
|
// What follows an op's colon, as a pattern: PAT_SPACE is a run of
|
|
// whitespace, PAT_DIGITS a run of digits, PAT_OPS the run of digits that is
|
|
// the op's ops/sec, PAT_HASH the run of hex digits that is its hash, and
|
|
// any other character itself.
|
|
#define PAT_SPACE ' '
|
|
#define PAT_DIGITS '#'
|
|
#define PAT_OPS '@'
|
|
#define PAT_HASH '%'
|
|
#define TIMED_PATTERN " # iters / # frames = @ ops/sec"
|
|
#define HASH_PATTERN TIMED_PATTERN " | hash=%"
|
|
#define CHECK_PATTERN " hash=%"
|
|
// The op is a whitespace character and at least one more before its colon.
|
|
#define OP_MIN_BYTES 2
|
|
#define DECIMAL 10u
|
|
|
|
// len bytes at text, not NUL-terminated.
|
|
typedef struct SpanT {
|
|
const char *text;
|
|
size_t len;
|
|
} SpanT;
|
|
|
|
// Where a line's op, ops/sec, and hash are.
|
|
typedef struct MatchT {
|
|
SpanT op;
|
|
SpanT opsPerSec;
|
|
SpanT hash;
|
|
} MatchT;
|
|
|
|
static uint64_t decimalValue(SpanT digits);
|
|
static void hashSet(ToolUberOpT *op, SpanT hash);
|
|
static bool matchAt(SpanT line, size_t at, const char *pattern, MatchT *m);
|
|
static bool matchLine(SpanT line, const char *prefix, const char *pattern, MatchT *m);
|
|
static size_t matchRun(SpanT line, size_t at, int (*isWanted)(int));
|
|
static size_t opIndex(const ToolUberLogT *log, const char *name, size_t nameLen);
|
|
static ToolUberOpT *opSet(ToolUberLogT *log, const char *prefix, SpanT name);
|
|
|
|
|
|
// A run of digits' value, held at UINT64_MAX past it.
|
|
static uint64_t decimalValue(SpanT digits) {
|
|
uint64_t value;
|
|
uint64_t digit;
|
|
size_t i;
|
|
|
|
value = 0u;
|
|
for (i = 0; i < digits.len; i++) {
|
|
digit = (uint64_t)(digits.text[i] - '0');
|
|
if (value > (UINT64_MAX - digit) / DECIMAL) {
|
|
return UINT64_MAX;
|
|
}
|
|
value = value * DECIMAL + digit;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
|
|
// An op's hash, upper case, in place of any it had.
|
|
static void hashSet(ToolUberOpT *op, SpanT hash) {
|
|
size_t i;
|
|
|
|
free(op->hash);
|
|
op->hash = (char *)malloc(hash.len + 1u);
|
|
if (op->hash == NULL) {
|
|
toolFail("out of memory");
|
|
}
|
|
for (i = 0; i < hash.len; i++) {
|
|
op->hash[i] = (char)toupper((unsigned char)hash.text[i]);
|
|
}
|
|
op->hash[hash.len] = '\0';
|
|
}
|
|
|
|
|
|
// The line from at (just past a prefix) as an op, its colon, and pattern.
|
|
static bool matchAt(SpanT line, size_t at, const char *pattern, MatchT *m) {
|
|
const char *colon;
|
|
const char *p;
|
|
size_t run;
|
|
|
|
colon = (const char *)memchr(line.text + at, ':', line.len - at);
|
|
if (colon == NULL || colon - (line.text + at) < OP_MIN_BYTES || !isspace((unsigned char)line.text[at])) {
|
|
return false;
|
|
}
|
|
m->op.text = line.text + at;
|
|
m->op.len = (size_t)(colon - m->op.text);
|
|
while (m->op.len > 0u && isspace((unsigned char)m->op.text[0])) {
|
|
m->op.text++;
|
|
m->op.len--;
|
|
}
|
|
while (m->op.len > 0u && isspace((unsigned char)m->op.text[m->op.len - 1u])) {
|
|
m->op.len--;
|
|
}
|
|
at = (size_t)(colon - line.text) + 1u;
|
|
for (p = pattern; *p != '\0'; p++) {
|
|
switch (*p) {
|
|
case PAT_SPACE:
|
|
run = matchRun(line, at, isspace);
|
|
break;
|
|
case PAT_DIGITS:
|
|
case PAT_OPS:
|
|
run = matchRun(line, at, isdigit);
|
|
break;
|
|
case PAT_HASH:
|
|
run = matchRun(line, at, isxdigit);
|
|
break;
|
|
default:
|
|
run = (at < line.len && line.text[at] == *p) ? 1u : 0u;
|
|
break;
|
|
}
|
|
if (run == 0u) {
|
|
return false;
|
|
}
|
|
if (*p == PAT_OPS) {
|
|
m->opsPerSec.text = line.text + at;
|
|
m->opsPerSec.len = run;
|
|
} else if (*p == PAT_HASH) {
|
|
m->hash.text = line.text + at;
|
|
m->hash.len = run;
|
|
}
|
|
at += run;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
// The first place in the line where prefix, an op, and pattern fit.
|
|
static bool matchLine(SpanT line, const char *prefix, const char *pattern, MatchT *m) {
|
|
size_t prefixLen;
|
|
size_t at;
|
|
|
|
prefixLen = strlen(prefix);
|
|
for (at = 0; at + prefixLen <= line.len; at++) {
|
|
if (memcmp(line.text + at, prefix, prefixLen) == 0 && matchAt(line, at + prefixLen, pattern, m)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
// The bytes of the run of characters isWanted takes from at.
|
|
static size_t matchRun(SpanT line, size_t at, int (*isWanted)(int)) {
|
|
size_t run;
|
|
|
|
run = 0u;
|
|
while (at + run < line.len && isWanted((unsigned char)line.text[at + run])) {
|
|
run++;
|
|
}
|
|
return run;
|
|
}
|
|
|
|
|
|
// The index of the op of that name, or the count when there is none.
|
|
static size_t opIndex(const ToolUberLogT *log, const char *name, size_t nameLen) {
|
|
size_t i;
|
|
|
|
for (i = 0; i < log->count; i++) {
|
|
if (log->ops[i].nameLen == nameLen && memcmp(log->ops[i].name, name, nameLen) == 0) {
|
|
break;
|
|
}
|
|
}
|
|
return i;
|
|
}
|
|
|
|
|
|
// The op named prefix and name: the one the log has, or a new one after
|
|
// the rest.
|
|
static ToolUberOpT *opSet(ToolUberLogT *log, const char *prefix, SpanT name) {
|
|
ToolUberOpT *op;
|
|
char *full;
|
|
size_t prefixLen;
|
|
size_t len;
|
|
size_t index;
|
|
|
|
prefixLen = strlen(prefix);
|
|
len = prefixLen + name.len;
|
|
full = (char *)malloc(len + 1u);
|
|
if (full == NULL) {
|
|
toolFail("out of memory");
|
|
}
|
|
memcpy(full, prefix, prefixLen);
|
|
memcpy(full + prefixLen, name.text, name.len);
|
|
full[len] = '\0';
|
|
index = opIndex(log, full, len);
|
|
if (index < log->count) {
|
|
free(full);
|
|
return &log->ops[index];
|
|
}
|
|
if (log->count == log->cap) {
|
|
log->cap = log->cap * 2u + 1u;
|
|
log->ops = (ToolUberOpT *)realloc(log->ops, log->cap * sizeof(ToolUberOpT));
|
|
if (log->ops == NULL) {
|
|
toolFail("out of memory");
|
|
}
|
|
}
|
|
op = &log->ops[log->count++];
|
|
memset(op, 0, sizeof(*op));
|
|
op->name = full;
|
|
op->nameLen = len;
|
|
return op;
|
|
}
|
|
|
|
|
|
const ToolUberOpT *toolUberFind(const ToolUberLogT *log, const char *name, size_t nameLen) {
|
|
size_t index;
|
|
|
|
index = opIndex(log, name, nameLen);
|
|
return index < log->count ? &log->ops[index] : NULL;
|
|
}
|
|
|
|
|
|
bool toolUberLoad(const char *path, ToolUberReadE what, ToolUberLogT *log) {
|
|
ToolUberOpT *op;
|
|
MatchT m;
|
|
SpanT line;
|
|
FILE *f;
|
|
char *text;
|
|
size_t cap;
|
|
ssize_t got;
|
|
bool ok;
|
|
int err;
|
|
|
|
memset(log, 0, sizeof(*log));
|
|
f = fopen(path, "rb");
|
|
if (f == NULL) {
|
|
return false;
|
|
}
|
|
text = NULL;
|
|
cap = 0u;
|
|
while ((got = getline(&text, &cap, f)) >= 0) {
|
|
line.text = text;
|
|
line.len = (size_t)got;
|
|
if (line.len > 0u && line.text[line.len - 1u] == '\n') {
|
|
line.len--;
|
|
}
|
|
if (line.len > 0u && line.text[line.len - 1u] == '\r') {
|
|
line.len--;
|
|
}
|
|
if (what == TOOL_UBER_PERF) {
|
|
if (matchLine(line, TIMED_PREFIX, TIMED_PATTERN, &m)) {
|
|
op = opSet(log, "", m.op);
|
|
op->opsPerSec = decimalValue(m.opsPerSec);
|
|
}
|
|
} else if (matchLine(line, TIMED_PREFIX, HASH_PATTERN, &m)) {
|
|
hashSet(opSet(log, "", m.op), m.hash);
|
|
} else if (matchLine(line, CHECK_PREFIX, CHECK_PATTERN, &m)) {
|
|
hashSet(opSet(log, CHECK_NAME, m.op), m.hash);
|
|
}
|
|
}
|
|
err = errno;
|
|
ok = !ferror(f);
|
|
free(text);
|
|
fclose(f);
|
|
errno = err;
|
|
return ok;
|
|
}
|
|
|
|
|
|
void toolUberNamePrint(FILE *f, const ToolUberOpT *op) {
|
|
fwrite(op->name, 1u, op->nameLen, f);
|
|
}
|
|
|
|
|
|
bool toolUberRead(const char *toolName, const char *path, ToolUberReadE what, ToolUberLogT *log) {
|
|
if (toolUberLoad(path, what, log)) {
|
|
return true;
|
|
}
|
|
fprintf(stderr, "%s: cannot read %s: %s\n", toolName, path, strerror(errno));
|
|
return false;
|
|
}
|