Heartbeat
The heartbeat subsystem periodically samples process and system health — CPU, memory, disk, threads, open files, and file descriptors — and publishes it as a liveness and resource-utilization signal. It is owned entirely by the library: the GGCommons runtime starts it at init and stops it at shutdown, with zero component code.
You never call the heartbeat API. You enable and shape it with a single heartbeat section in your
component config; the runtime does the rest.
What it measures
Section titled “What it measures”When a measure is enabled, the heartbeat collects it into a nested stats object of the shape
{ cpu, memory, disk, threads, files, fds } (disabled measures are omitted). The metric target
flattens this to one metric value per measure; the messaging target sends the nested object as the
message payload.
Counter availability varies by language and platform. Where a counter is enabled but unavailable on the current platform, the value falls back as noted.
| Measure | Unit | Availability and fallbacks |
|---|---|---|
cpu |
Percent | All languages, all platforms. |
memory |
MB (resident set) | All languages, all platforms. Java reports whole-number MB (integer division); Python, Rust, and TypeScript report fractional MB. |
disk |
Gigabytes (disk_total, disk_used, disk_free; disk_used = disk_total − disk_free) |
All languages, all platforms. Measured for the current working directory’s mount. |
threads |
Count | Java and Python: all platforms. Rust: Linux and Windows. TypeScript: Linux only (returns 0 elsewhere). |
files |
Count (open files) | Java and Python: all platforms. Rust: Linux (regular-file descriptors) and Windows (total handles). TypeScript: Linux only (returns 0 elsewhere). |
fds |
Count (open file descriptors) | Java: Unix only (-1 on Windows). Python: all platforms (Windows reports handle count). Rust: Linux and Windows. TypeScript: Linux only (returns 0 elsewhere). |
How it is published
Section titled “How it is published”A heartbeat fires on its interval and is routed to one or more targets. Two target types exist:
metric— emits one metric per enabled measure through the metrics subsystem (metric nameheartbeat). The metric’sstorageResolutionis1when the interval is under 60 seconds, otherwise60.messaging— publishes the nested stats object through the messaging subsystem, to a configurabledestinationandtopic.
The default interval is 5 seconds in all four languages. The messaging envelope carries a fixed message name and version:
| Field | Value |
|---|---|
| Message name | heartbeat (Java, Rust, TypeScript) · Heartbeat (Python — capital H) |
| Message version | 1.0.0 (all languages) |
Destination and topic
Section titled “Destination and topic”The messaging target’s config accepts:
destination— where to publish. Recognized values areipc/local(the local broker or Greengrass IPC) andiot_core/iotcore(AWS IoT Core, published at QoS at-least-once). The default isipc. Any other value is logged as unrecognized and skipped.topic— the publish topic. The default isggcommons/{ThingName}/{ComponentName}/heartbeat. The{ThingName}and{ComponentName}template variables are resolved at publish time.
Configuration
Section titled “Configuration”Add a heartbeat section to your component config file (the same config source you select with
-c). Because the cross-language defaults differ (see below), the most portable approach is to
specify intervalSecs, measures, and targets explicitly.
{ "heartbeat": { "intervalSecs": 5, "measures": { "cpu": true, "memory": true, "disk": false, "threads": false, "files": false, "fds": false }, "targets": [ { "type": "metric" }, { "type": "messaging", "config": { "destination": "ipc", "topic": "ggcommons/{ThingName}/{ComponentName}/heartbeat" } } ] }}A minimal metric-only heartbeat is just:
{ "heartbeat": { "intervalSecs": 30, "measures": { "cpu": true, "memory": true }, "targets": [{ "type": "metric" }] }}Cross-language differences to know
Section titled “Cross-language differences to know”The heartbeat is config-driven and consistent in spirit, but a few behaviors diverge. Being explicit in config (above) avoids most of them.
- Defaults when
targetsis omitted. Java injects a single{ "type": "metric" }target; Python defaults to a singlemessaging/ipctarget on the default topic; Rust and TypeScript publish nothing (empty target list). Always settargetsexplicitly for predictable behavior. - Defaults when
measuresis omitted. Java and Python default tocpuandmemoryenabled (rest off); Rust and TypeScript default all measures off, so an omittedmeasuresblock collects nothing. Always setmeasuresexplicitly. - First-tick timing. Java, Rust, and TypeScript fire the first heartbeat at roughly
t=0. Python waits one full interval before its first publish. - Interval floor. Java, Rust, and TypeScript enforce a minimum (Java resets a value below
1to5; Rust and TypeScript clamp to a minimum of1). Python applies no floor — a sub-second or zero interval is honored as-is and will busy-loop, so do not set a tiny interval in Python. - Messaging service requirement. In Java a messaging service is always present (the heartbeat is
wired with both messaging and metric services). In Python, Rust, and TypeScript messaging is
optional — a
messagingtarget with no messaging service available is logged and skipped.
Every tick is exception-guarded in all four languages: a failing sample is logged and the next tick still fires.
Observe heartbeats locally
Section titled “Observe heartbeats locally”With the HOST platform and the MQTT transport, subscribe to the heartbeat topic to watch heartbeats
arrive. The default topic has four segments (ggcommons/<thing>/<component>/heartbeat), so the
matching wildcard filter is ggcommons/+/+/heartbeat.
# Bring up the local broker (EMQX), then subscribe to all heartbeats.docker compose -f test-infra/compose.yaml up -dmosquitto_sub -h localhost -p 1883 -t 'ggcommons/+/+/heartbeat' -v# Bring up the local broker (EMQX), then subscribe to all heartbeats.docker compose -f test-infra/compose.yaml up -dmosquitto_sub -h localhost -p 1883 -t 'ggcommons/+/+/heartbeat' -v