config: configure thread counts with ${NPROC}/${THREAD_FACTOR} expressions
The worker-thread knobs grew a special case in C++: thread-max was either
a bare integer or, when blank, ceil(hardware_concurrency() * thread-factor),
and thread-min/loadavg-target were parsed with stoul(). None of them went
through the config substitution or the size-expression grammar, so the
CPU-relative arithmetic was stuck in bees-context.cc instead of the config
where the defaults belong.
Move that arithmetic into the config expression:
- Add a ${NPROC} substitution variable (logical CPU count, clamped to at
least 1, since hardware_concurrency() may legally return 0).
- Expose the merged thread.thread-factor value as ${THREAD_FACTOR}. Unlike
the other subst variables this derives from a config key rather than a
system fact, so it is populated in set_argv() once all config layers are
merged, not in the constructor. The normalized numeric value is stored,
not the raw string, because bees_parse_ratio accepts "3/2" and "15:10"
forms that the size-expression grammar cannot parse.
- Parse thread-min and thread-max as size expressions (subst() then
bees_parse_size()), and drop the C++ blank/ceil special case.
thread-factor now reaches the thread count only via the default thread-max
expression, max(1, ceil(${NPROC} * ${THREAD_FACTOR})).
- Add a one-argument ceil() function to the size-expression grammar so the
default can round up like the old code did.
- Parse loadavg-target with a new bees_parse_size_double(): a target load
average is inherently fractional, and the released code used stod().
bees_parse_size() would truncate "0.5" to 0, which means "throttling
disabled" -- the worst possible misreading. bees_parse_size() now
delegates to bees_parse_size_double() and only adds the integer cast.
Command-line semantics are unchanged from the released code: -c N sets an
exact thread count (thread-factor ignored, oversubscription allowed), since
it simply overrides the thread-max key with the literal N.