Compare commits
1 Commits
feat/f3-hb
...
feat/f3-pi
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a80378e73 |
@@ -6,7 +6,7 @@ MOSAIC_TMUX_SOCKET=${MOSAIC_TMUX_SOCKET:-mosaic-factory}
|
|||||||
MOSAIC_AGENT_RUNTIME=${MOSAIC_AGENT_RUNTIME:-pi}
|
MOSAIC_AGENT_RUNTIME=${MOSAIC_AGENT_RUNTIME:-pi}
|
||||||
MOSAIC_AGENT_WORKDIR=${MOSAIC_AGENT_WORKDIR:-$HOME}
|
MOSAIC_AGENT_WORKDIR=${MOSAIC_AGENT_WORKDIR:-$HOME}
|
||||||
MOSAIC_AGENT_COMMAND=${MOSAIC_AGENT_COMMAND:-}
|
MOSAIC_AGENT_COMMAND=${MOSAIC_AGENT_COMMAND:-}
|
||||||
MOSAIC_HEARTBEAT_RUN_DIR=${MOSAIC_HEARTBEAT_RUN_DIR:-$HOME/.config/mosaic/fleet/run}
|
MOSAIC_HEARTBEAT_RUN_DIR=${MOSAIC_HEARTBEAT_RUN_DIR:-${MOSAIC_HOME:-$HOME/.config/mosaic}/fleet/run}
|
||||||
MOSAIC_HEARTBEAT_INTERVAL=${MOSAIC_HEARTBEAT_INTERVAL:-15}
|
MOSAIC_HEARTBEAT_INTERVAL=${MOSAIC_HEARTBEAT_INTERVAL:-15}
|
||||||
|
|
||||||
if [ -z "$AGENT_NAME" ]; then
|
if [ -z "$AGENT_NAME" ]; then
|
||||||
|
|||||||
@@ -851,6 +851,23 @@ describe('fleet ps — heartbeat parsing', () => {
|
|||||||
expect(hb.health).toBe('unknown');
|
expect(hb.health).toBe('unknown');
|
||||||
expect(hb.ts).toBeNull();
|
expect(hb.ts).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('honors MOSAIC_HEARTBEAT_INTERVAL for the freshness threshold', () => {
|
||||||
|
const prev = process.env.MOSAIC_HEARTBEAT_INTERVAL;
|
||||||
|
try {
|
||||||
|
// A 60s-old beat is STALE at the default 15s interval (3x15 = 45s)...
|
||||||
|
const ts = new Date(NOW - 60_000).toISOString();
|
||||||
|
const content = `ts=${ts}\npid=1\nstatus=ok\n`;
|
||||||
|
delete process.env.MOSAIC_HEARTBEAT_INTERVAL;
|
||||||
|
expect(parseHeartbeat(content, NOW).health).toBe('stale');
|
||||||
|
// ...but HEALTHY when the operator widened the interval to 30s (3x30 = 90s).
|
||||||
|
process.env.MOSAIC_HEARTBEAT_INTERVAL = '30';
|
||||||
|
expect(parseHeartbeat(content, NOW).health).toBe('healthy');
|
||||||
|
} finally {
|
||||||
|
if (prev === undefined) delete process.env.MOSAIC_HEARTBEAT_INTERVAL;
|
||||||
|
else process.env.MOSAIC_HEARTBEAT_INTERVAL = prev;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('fleet ps — systemd show parsing', () => {
|
describe('fleet ps — systemd show parsing', () => {
|
||||||
|
|||||||
@@ -368,6 +368,16 @@ export function buildAgentTailCommand(
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export const HEARTBEAT_INTERVAL_MS = 15_000;
|
export const HEARTBEAT_INTERVAL_MS = 15_000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Heartbeat interval in ms, honoring MOSAIC_HEARTBEAT_INTERVAL (seconds) so the
|
||||||
|
* `fleet ps` freshness threshold matches the writer sidecar's actual cadence
|
||||||
|
* (start-agent-session.sh). Falls back to HEARTBEAT_INTERVAL_MS (15s).
|
||||||
|
*/
|
||||||
|
export function heartbeatIntervalMs(): number {
|
||||||
|
const sec = Number.parseInt(process.env.MOSAIC_HEARTBEAT_INTERVAL ?? '', 10);
|
||||||
|
return Number.isFinite(sec) && sec > 0 ? sec * 1000 : HEARTBEAT_INTERVAL_MS;
|
||||||
|
}
|
||||||
export const HEARTBEAT_HEALTHY_MULTIPLIER = 3;
|
export const HEARTBEAT_HEALTHY_MULTIPLIER = 3;
|
||||||
|
|
||||||
export interface HeartbeatInfo {
|
export interface HeartbeatInfo {
|
||||||
@@ -496,7 +506,7 @@ export function parseHeartbeat(content: string | null, nowMs = Date.now()): Hear
|
|||||||
status = val;
|
status = val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const thresholdMs = HEARTBEAT_INTERVAL_MS * HEARTBEAT_HEALTHY_MULTIPLIER;
|
const thresholdMs = heartbeatIntervalMs() * HEARTBEAT_HEALTHY_MULTIPLIER;
|
||||||
let health: 'healthy' | 'stale' | 'unknown' = 'unknown';
|
let health: 'healthy' | 'stale' | 'unknown' = 'unknown';
|
||||||
let ageMs: number | null = null;
|
let ageMs: number | null = null;
|
||||||
if (ts !== null) {
|
if (ts !== null) {
|
||||||
|
|||||||
Reference in New Issue
Block a user