Graph Provider Configuration

Configuration reference for the ros2_medkit_graph_provider plugin, which serves the x-medkit-graph vendor extension. For the /diagnostics producer contract, plugin loading, and a worked example, see Graph Provider Plugin first - this page only covers the tunable thresholds.

Configuration Keys

All keys are set under the plugins.graph_provider. prefix in gateway_params.yaml, alongside plugins.graph_provider.path (see Plugin System):

Parameter

Default

Description

expected_frequency_hz_default

30.0

Fallback expected frequency (Hz) used to compute an edge’s frequency ratio when no per-message expected_frequency was stamped into /diagnostics and no per-function override applies.

degraded_frequency_ratio

0.5

An edge is degraded when measured_frequency_hz / expected_frequency_hz falls below this ratio.

drop_rate_percent_threshold

5.0

An edge is degraded when drop_rate_percent exceeds this value.

freshness_floor_sec

5.0

Minimum freshness window in seconds, regardless of expected frequency. Keeps slow producers (e.g. a 1 Hz reference monitor) from flapping between active and error on ordinary sample-to-sample jitter.

freshness_headroom_factor

3.0

Multiplier applied to the expected message interval when computing the freshness window (see formula below).

stale_grace_sec

2.0

Extra seconds an edge may remain outside its freshness window before metrics_status flips to error/metrics_stale. Absorbs a single late DDS/executor-jitter /diagnostics sample so pipeline_status does not flap to broken and back on the very next on-time sample. 0.0 disables the grace (report stale exactly at the window boundary - today’s behavior before this option existed). See Stale-Grace Debounce below.

multi_publisher_rate

"annotate"

Policy for edges whose DATA topic has more than one live publisher: "annotate" always shows the measured rate and flags the ambiguity; "suppress" hides the (untrustworthy) rate instead. See Multi-Publisher Rate Policy below.

All seven keys are validated on load: expected_frequency_hz_default, degraded_frequency_ratio, freshness_headroom_factor, and freshness_floor_sec must be strictly positive; drop_rate_percent_threshold and stale_grace_sec must be non-negative (zero is a valid value for both - a zero threshold and a zero grace are both meaningful, not “disabled”); and multi_publisher_rate must be exactly "annotate" or "suppress". An invalid value is rejected, logged as a warning naming the offending key and value, and the plugin’s own hardcoded default is used instead - it never silently disables degradation or staleness detection by falling through to a non-positive divisor.

Freshness Window

An edge’s freshness window - how long a merged sample stays active before the edge flips to error/metrics_stale if nothing newer arrives - is derived from the resolved expected frequency, not a single fixed timeout:

freshness_window_sec = max(
    freshness_floor_sec,
    freshness_headroom_factor / expected_frequency_hz
)

A topic expected at 30 Hz with the defaults gets a window of max(5.0, 3.0 / 30.0) = 5.0 seconds (the floor dominates). A topic overridden to 2 Hz gets max(5.0, 3.0 / 2.0) = 5.0 seconds too, still floor-dominated; only quite slow expected rates (below freshness_headroom_factor / freshness_floor_sec = 0.6 Hz with the defaults) push the window above the floor.

Example: raising the floor for a deliberately slow diagnostic producer:

ros2_medkit_gateway:
  ros__parameters:
    plugins: ["graph_provider"]
    plugins.graph_provider.freshness_floor_sec: 15.0

Stale-Grace Debounce

An edge is not reported metrics_stale the instant its age crosses the freshness window above - it must stay outside that window continuously for more than stale_grace_sec before metrics_status flips to error:

metrics_stale iff age_sec > freshness_window_sec + stale_grace_sec

This is a stateless function of the sample’s age, the resolved freshness window, and the grace period - there is no separate onset/tick state to maintain, so it always uses whichever window the edge actually resolved (its own per-function config, never a different function’s or the global default). The default, 2.0 seconds, absorbs ordinary DDS/executor jitter; raise it for a noisier network or executor, or set it to 0.0 for immediate, point-in-time detection (the plugin’s original behavior before this option existed). stale_grace_sec can be overridden per Function like every other threshold on this page.

Multi-Publisher Rate Policy

frequency_hz (mapped from the producer’s frame_rate_msg/frame_rate_node, see Graph Provider Plugin) is a topic-level arrival rate measured on the subscriber side of the ``/diagnostics`` producer, summed across every live publisher on that topic name - the graph provider has no way to attribute an incoming sample to one specific publisher. If a second, leftover, or duplicate publisher ends up on the same topic, its messages inflate the summed rate, and a genuinely slow or stalled primary publisher can read as healthy.

To make that ambiguity visible, every edge whose DATA topic (queried live from the ROS graph, independent of and in addition to /diagnostics) resolves more than one live publisher always gets metrics.publisher_count (the live count) and metrics.rate_ambiguous: true in its response, regardless of policy - see REST API Reference’s field notes. multi_publisher_rate controls what happens to frequency_hz itself once that is true:

Value

Behavior

"annotate" (default)

Keep showing the measured frequency_hz and let it drive the degraded ratio as usual; publisher_count/rate_ambiguous warn the operator that the number may be inflated, but the plugin still trusts it.

"suppress"

Omit frequency_hz entirely and never let it drive a degraded verdict for this edge. Freshness/metrics_status is unaffected - suppression is only about the rate number, never about whether the edge is stale.

Choose "annotate" when you want visibility without losing data. Choose "suppress" for a safety-critical deployment that would rather show no rate than an untrustworthy one when more than one publisher is present. Suppression removes the misleading number, not the edge’s health verdict: rate_ambiguous flags the ambiguity and pipeline_status is unchanged. multi_publisher_rate can be overridden per Function like every other setting on this page.

Per-Function Overrides

Every threshold above can be overridden for a specific Function using plugins.graph_provider.function_overrides.<function_id>.<field>. This lets different subsystems run different health baselines - a camera pipeline expected at 30 Hz next to a LiDAR pipeline expected at 10 Hz, in the same deployment.

Important

The override field names are not the same strings as the global default keys. Most notably: the global default is expected_frequency_hz_default, but the per-function override field is expected_frequency_hz (no _default suffix). The other six fields keep the same name in both places:

Global default key

Per-function override field

expected_frequency_hz_default

expected_frequency_hz

degraded_frequency_ratio

degraded_frequency_ratio

drop_rate_percent_threshold

drop_rate_percent_threshold

freshness_headroom_factor

freshness_headroom_factor

freshness_floor_sec

freshness_floor_sec

stale_grace_sec

stale_grace_sec

multi_publisher_rate

multi_publisher_rate

A function with no override for a given field inherits that field from the resolved global defaults, not from a separate hardcoded fallback. A numeric override value is validated with the same positive/non-negative rules as its global counterpart, and a multi_publisher_rate override is validated against the same {"annotate", "suppress"} enum as its global counterpart; a rejected override falls back to whatever the function’s config already resolved to (an earlier valid override for the same field, or the validated global default) - it can never smuggle in a value the global defaults would themselves reject (e.g. a zero expected_frequency_hz).

Warning

Per-function overrides only take effect via a params YAML loaded with config_file. Two other ways an operator might expect to set one either silently do nothing or fail outright:

  • A bare ros2 launch ros2_medkit_gateway gateway.launch.py plugins.graph_provider.function_overrides.<function_id>.<field>:=value is dropped without warning: gateway.launch.py only forwards a fixed set of launch arguments into the node’s parameters (server_host, server_port, refresh_interval_ms, cors_allowed_origins, config_file) - there is no passthrough for arbitrary plugin parameters.

  • ros2 run ... --ros-args -p plugins.graph_provider.function_overrides.<function_id>.<field>:=value fails to parse whenever <function_id> contains a hyphen (e.g. lidar-pipeline, engine-monitoring) - rcl’s parameter-value lexer rejects the hyphen there, and the command errors out before the gateway starts.

The only reliable path is a YAML params file with the flat, dotted keys (see the worked example below) loaded via config_file:

ros2 launch ros2_medkit_gateway gateway.launch.py \
  config_file:=/path/to/graph-provider-overrides.yaml

Worked Example

ros2_medkit_gateway:
  ros__parameters:
    plugins: ["graph_provider"]
    plugins.graph_provider.expected_frequency_hz_default: 30.0
    plugins.graph_provider.degraded_frequency_ratio: 0.5

    # camera-pipeline Function stays at the 30 Hz global default (no override needed)

    # lidar-pipeline Function: 10 Hz sensor, wider drop-rate tolerance
    plugins.graph_provider.function_overrides.lidar-pipeline.expected_frequency_hz: 10.0
    plugins.graph_provider.function_overrides.lidar-pipeline.drop_rate_percent_threshold: 8.0

    # engine-monitoring Function: slow 2 Hz sensor
    plugins.graph_provider.function_overrides.engine-monitoring.expected_frequency_hz: 2.0

Each dotted plugins.graph_provider.function_overrides.<function_id>.<field> ROS 2 parameter is delivered to the plugin as a nested JSON object (the gateway builds nested objects from dotted parameter names for every plugin, not just this one). For the lidar-pipeline override above, configure() receives:

{
  "expected_frequency_hz_default": 30.0,
  "degraded_frequency_ratio": 0.5,
  "function_overrides": {
    "lidar-pipeline": {
      "expected_frequency_hz": 10.0,
      "drop_rate_percent_threshold": 8.0
    },
    "engine-monitoring": {
      "expected_frequency_hz": 2.0
    }
  }
}

Only numeric values are accepted for the six numeric override fields, and only a string value for multi_publisher_rate. A non-numeric value for a numeric field, a non-string value for multi_publisher_rate, or an unrecognized field name never crashes configuration load, but it also no longer applies silently: each case logs a warning naming the offending function, field, and value, and the override is skipped in favor of whatever that function’s config already resolved to.

See Also