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
268 lines
13 KiB
C
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
|