myxce/src/include/utils.h
Rongsong Shen eb23c158ae
feat(scheduler-sim-diagnostics): promote byte-unit formatting to shared lib (task 7)
Adds Requirement 6 (Human-Readable Units for Byte-Valued Limit Amounts)
to the spec: requirements, design, research, and tasks 7-10.

Task 7 (Foundation): promotes the REPL's byte-unit scaling algorithm
and byte-valued-resource classifier into src/lib/utils.c/utils.h
(xce_format_bytes_human, xce_is_bytes_valued_resource), alongside a
new display-mode-aware composer (xce_format_units_value,
xce_units_mode_t) -- following the fmt_jobid()/fmt_jobid_ex()
precedent. repl_fmt.c's scheduler-only functions become one-line
delegating wrappers. repl_info.c's repl_format_numeric_resource_value()
(shared by info worker/info resource, not just the upcoming info
limit) is retyped to delegate to the new shared composer via a small
translate_units_mode() bridge, so every consumer benefits from one
implementation going forward.

No existing symbol's signature changed; only new symbols were added.
Confirmed behavior-preserving: full scheduler suite 85/85 unchanged,
including info worker/info resource's own regression tests. One
narrow, untested, out-of-requirement-scope corner case found and
documented (swp's RAW/BOTH mode rendering under the new single-value
composer interface) -- logged in tasks.md's Implementation Notes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC
2026-09-20 04:40:44 +00:00

268 lines
13 KiB
C

#if !defined(_UTILS_H_)
#define _UTILS_H_
#include "config.h"
#include <capnp_c.h>
#include <math.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <tlog.h>
#include "controller.capnp.h"
#include "xce/errnox.h"
#include "xce/types.h"
#define CONTAINER_OF(P, T, F) ((T*)(((char*)(P)) - offsetof(T, F)))
#define MAKE_JOBID(cluster, apiserver, timestamp, tag, sequence) \
((((uint64_t)cluster) << CLUSTER_ID_SHIFT) \
| (((uint64_t)apiserver) << APISERVER_ID_SHIFT) \
| (((uint64_t)timestamp) << TIMESTAMP_SHIFT) \
| (((uint64_t)tag) << TAG_SHIFT) | (sequence))
#define EXTRACT_BITS(id, shift, bits) (((id) >> (shift)) & (UMAX_BITS(bits)))
#define EXTRACT_ID_TAG(id) EXTRACT_BITS(id, TAG_SHIFT, N_TAG_BITS)
#define EXTRACT_ID_CLUSTER(id) \
EXTRACT_BITS(id, CLUSTER_ID_SHIFT, N_CLUSTER_ID_BITS)
#define EXTRACT_ID_APISERVER(id) \
EXTRACT_BITS(id, APISERVER_ID_SHIFT, N_APISERVER_ID_BITS)
#define EXTRACT_ID_TIMESTAMP(id) \
EXTRACT_BITS(id, TIMESTAMP_SHIFT, N_TIMESTAMP_BITS)
#define EXTRACT_ID_SEQUENCE(id) EXTRACT_BITS(id, 0, N_SEQUENCE_BITS)
#define EXTRACT_ARRAY_JOB(id) \
((((id) >> SEQUENCE_HIGH_SHIFT) \
& UMAX_BITS(N_SEQUENCE_BITS - SEQUENCE_HIGH_SHIFT)) \
<< SEQUENCE_HIGH_SHIFT)
#define EXTRACT_ARRAY_BASEID(id) \
MAKE_JOBID(EXTRACT_ID_CLUSTER(id), EXTRACT_ID_APISERVER(id), \
EXTRACT_ID_TIMESTAMP(id), 0x0, EXTRACT_ARRAY_JOB(id))
#define EXTRACT_ARRAY_IDX(id) EXTRACT_BITS(id, 0, N_ARRAY_IDX_BITS)
#define ROUNDUP(v, bits) (((v) + UMAX_BITS(bits)) & ~UMAX_BITS(bits))
#define xce_use(x) (void)(x)
#define float_near_zero (0.000001)
#define float_equal(a, b) (fabs((a) - (b)) < float_near_zero)
#define STR(x) _STR(x)
#define _STR(x) #x
#define MK_VERSION(ver, commit) ver commit
/* SUPERSEDED for version-string composition (build-time-versioning task
* 3.1). MK_VERSION/STR (immediately above) cannot express an
* empty-value -> "unknown" fallback: an unset commit_id/build_time meson
* option makes STR(x) expand to an empty string literal ("", not a missing
* token), and a preprocessor macro has no way to branch on "is this string
* empty" at expansion time. xce_version_field()/xce_compose_version()
* (extern decls below, implementation + doc comments in utils.c) are the
* runtime replacement — they DO perform that substitution, and are what
* compose the version string everywhere it now matters. Use those, not
* MK_VERSION, for any new version-string composition.
*
* MK_VERSION/STR are kept in place here (not deleted) only because task 3.2
* — which will migrate controller/main.c's and scheduler/src/main.c's
* argp_program_version construction to xce_compose_version() — has not
* landed yet. Grep finding (2026-08-20, `grep -rn 'STR(\|MK_VERSION(' src/`,
* excluding NULLSTR/comment/definition matches): STR() has ZERO call sites
* anywhere in this codebase outside this exact MK_VERSION-based
* argp_program_version pattern — controller/main.c:23,
* scheduler/src/main.c:26, AND controller/testsuite/test.c:662 (the
* xce_test REPL tool). That third caller is NOT listed in task 3.2's
* boundary (`controller/main.c, scheduler/main.c` only, per tasks.md) —
* worth confirming whether it's in scope when 3.2 lands, or it will keep
* printing a commit-id-only, build-time-less version string after the
* other two are migrated. STR() itself is a generic single-argument
* stringification macro (not inherently version-specific), but per this
* grep it currently has no unrelated use, so it is not being kept around
* for any other live caller — only in service of the not-yet-migrated
* MK_VERSION call sites above. Once task 3.2 (and any update to
* controller/testsuite/test.c) lands and no caller of MK_VERSION remains,
* both MK_VERSION and this STR usage can be deleted outright — not done
* here, out of this task's boundary.
*
* NOTE FOR TASK 3.2 (or whoever migrates argp_program_version):
* xce_compose_version() is a REGULAR RUNTIME FUNCTION, not a constant
* expression — unlike MK_VERSION's pure string-literal concatenation, it
* CANNOT initialize a file-scope variable. `const char* argp_program_version
* = xce_compose_version(...)` at file scope will not compile (C file-scope
* initializers must be constant expressions). controller/main.c:22-23 and
* scheduler/src/main.c:25-26 currently declare argp_program_version exactly
* that way — a file-scope const initialized at compile time — so this is
* not a one-line macro-call swap. Instead: compute the version string at
* RUNTIME into a `static char buf[...]` (module scope or local to main()),
* via xce_compose_version(), early in main() and BEFORE argp_parse() runs
* (argp reads argp_program_version by pointer when --version/-V is
* processed) — then assign argp_program_version (no longer const-qualified
* at its declaration; assigned at runtime instead of initialized) to point
* at that buffer. See xce_compose_version()'s doc comment in utils.c for
* the buffer-sizing contract. */
#ifndef FREEUP
#define FREEUP(p) \
if ((p) != NULL) { \
free(p); \
(p) = NULL; \
}
#endif
#define EQUAL_STRING(s1, s2) \
(((s1) == (s2)) \
|| (((s1) != NULL) && ((s2) != NULL) && (strcmp((s1), (s2)) == 0)))
#define N_EL_ARRAY(arr) (sizeof(arr) / sizeof(arr[0]))
#define PTR_STEP(p, delta) ((void*)(((char*)p) + (delta)))
/* move the owner ship of pointer from src to dst */
#define MOVE_PTR(dst, src) \
do { \
(dst) = (src); \
(src) = NULL; \
} while (0)
#define NULLSTR(p) (((p) == NULL) || (strlen(p) == 0))
#define SET_ERROR(code) \
do { \
set_error((char*)__FILE__, (char*)__func__, __LINE__, (code)); \
} while (0)
#define TRACE(...) tlog_debug3(__VA_ARGS__)
/* Named DBG, not DEBUG -- DEBUG is a bare command-line macro (-DDEBUG,
* src/meson.build) defined for both the debug and asan buildtypes, so
* `#define DEBUG(...)` here would trigger a "DEBUG redefined" warning in
* every translation unit that includes this header under either profile. */
#define DBG(...) tlog_debug(__VA_ARGS__)
#define INFO(...) tlog_info(__VA_ARGS__)
#define ERROR(...) tlog_error(__VA_ARGS__)
#define WARN(...) tlog_warn(__VA_ARGS__)
#define PERROR(s) xce_perror(s)
typedef struct {
char* file;
char* func;
int line;
int code;
} ectx_t;
extern int parse_argv(char*, char***);
extern void free_argv(char**, int);
extern int get_myname(char*, size_t);
extern int mylog_init(char*, char*, int, int);
extern void mylog_fini(void);
extern int mylog_setlevel(char*);
extern char* verbose_to_loglevel(int);
/* See the SUPERSEDED/NOTE comment above MK_VERSION for what these replace
* and why, and the constraint they impose on any file-scope
* argp_program_version-style global (xce_compose_version() is not a
* constant expression). */
extern const char* xce_version_field(const char*);
extern int xce_compose_version(char*, size_t, const char*, const char*,
const char*);
/* dirty-content-fingerprint task 3.1: --version-only sibling of
* xce_compose_version() above. Same composition (prefix + commit +
* " built " + build_time), plus one more optional field: when
* dirty_fingerprint is non-empty, a trailing "-dirty" on commit is
* stripped and "-<dirty_fingerprint>" is appended in its place, producing
* "<commit>-<dirty_fingerprint>". When dirty_fingerprint is NULL or empty,
* this function delegates straight to xce_compose_version() and produces
* byte-identical output -- commit_id's own computation and its existing
* -dirty suffix are never touched by this function. The startup-log
* composition call sites (controller/scheduler/worker main.c) must keep
* calling xce_compose_version() directly, never this function -- this is
* what keeps the startup log's format unchanged per Requirement 3.2 of the
* dirty-content-fingerprint spec. */
extern int xce_compose_version_ex(char*, size_t, const char*, const char*,
const char*, const char*);
extern void set_error(char*, char*, int, int);
extern ectx_t* get_ectx(void);
extern int xce_vprintf(const char*, va_list);
extern int xce_strerror_r(int, char*, size_t);
extern void xce_perror(const char*);
extern char* frame_symbol(int);
extern int copy_int_list(int**, int*, int);
extern int copy_strlist(xce_strlist_t*, xce_strlist_t);
extern void free_timewindow_config(xce_timewindow_config_t*);
extern xce_timewindow_config_t* dup_timewindow_config(xce_timewindow_config_t*);
extern void free_resource_thresholds(xce_res_threshold_t**, int);
extern xce_res_threshold_t** dup_resource_thresholds(xce_res_threshold_t**,
int);
extern void free_name_val_list(xce_name_val_t***, int*);
extern void free_strlist(xce_strlist_t*);
extern int fmt_jobid_ex(char*, size_t, uint64_t, char*, char*);
extern int fmt_jobid(char*, size_t, uint64_t);
extern int parse_jobid(uint64_t*, char*);
extern uint64_t array_jobid(uint64_t);
extern uint64_t array_idx(uint64_t);
/*
* xce_units_mode_t / xce_format_bytes_human() / xce_is_bytes_valued_resource()
* / xce_format_units_value() -- scheduler-sim-diagnostics spec, task 7 (Req
* 6.1, 6.3, 6.5). Shared-library promotion of the scheduler REPL's
* byte-unit scaling algorithm (formerly repl_fmt.c's
* repl_format_bytes_human()), its byte-valued-resource classification
* (formerly repl_fmt.c's repl_is_bytes_valued_resource()), and a
* display-mode-aware value composer (formerly repl_info.c's
* repl_format_numeric_resource_value()'s byte-valued branch), so any
* `libxce` consumer -- not only the scheduler's REPL -- can reach this
* display-only formatting. `fmt_jobid()`/`fmt_jobid_ex()` immediately above
* are the precedent this follows: a small, generic, cross-component
* display-formatting helper with no domain coupling.
*
* All three functions are pure and display-only -- none of them ever
* mutate a caller-supplied value or feed back into a stored/wire value.
* `xce_is_bytes_valued_resource()` is a fixed 5-name list (`mem`, `tmp`,
* `maxmem`, `maxtmp`, `maxswap`); a `NULL` name returns `false`.
* `xce_format_bytes_human()` adaptively scales `bytes` to the smallest
* B/KB/MB/GB/TB/PB unit it fits, writing e.g. "20.56 GB" into `buf`.
* `xce_format_units_value()` assumes the caller has already decided
* `bytes_value` is byte-valued (via `xce_is_bytes_valued_resource()` or the
* caller's own equivalent check) -- it does not re-derive that decision:
* RAW renders the bare "%f" of `bytes_value` alone, HUMAN renders
* `xce_format_bytes_human()`'s scaled form alone, and BOTH (the default for
* any other mode value) renders "<raw> (<human>)".
*
* `xce_repl_units_mode_t` (scheduler-private, `repl_internal.h`) is a
* deliberately separate, independently-defined enum with the same three
* semantic values -- a `src/lib` type must not be defined in terms of a
* scheduler-internal concept. The scheduler bridges the two at its one call
* site (`repl_info.c`'s `translate_units_mode()`).
*/
typedef enum {
XCE_UNITS_RAW = 1,
XCE_UNITS_HUMAN,
XCE_UNITS_BOTH
} xce_units_mode_t;
extern void xce_format_bytes_human(double bytes, char* buf, size_t bufsz);
extern bool xce_is_bytes_valued_resource(const char* name);
extern void xce_format_units_value(xce_units_mode_t mode, double bytes_value,
char* buf, size_t bufsz);
extern void unblock_sig(int);
extern void block_sig(int);
extern uint32_t hash4(uint32_t, uint32_t, uint32_t, uint32_t);
extern uint32_t hash64(uint64_t);
extern uint32_t hash_string(const char*);
extern uint32_t hash_block(char*, size_t);
#endif