Subchapter 51.12
references/cloudwatch-omni/service-health.mdMarkdown18 KBView on GitHub
How to answer “is <service> healthy“, “give me a health summary of <service>“,
““, and ““ — grounded in the context graph and confirmed with telemetry,
never from a resource’s control plane and never as a bare verdict. (Pure blast radius —
“what breaks if goes down” — belongs to ,
which owns upstream/blast-radius traversal; this file uses that traversal only to place a
dependency’s role and to confirm a caller’s health.)
<service> depend on and what role does each dependency play<service><datastore>This file is the investigation method that ties three surfaces together. It does not restate them:
GetContextGraph. The
traversal mechanics, edge semantics, pagination, truncation, and blast-radius/upstream
walk live in context-graph.md; this file uses them and cites the
roles each edge type assigns.traces.default / logs.default,
durationNano, and status['code'] are in
query/sql-logs-traces.md.OK / WARNING / CRITICAL / NODATA) and
how to read it with ListAlerts / GetAlert are in alerts.md.“Is <service> healthy,” “health summary of <service>,” “what does <service> depend
on and what role does each play,” and “which services call <service>“ are
observability-data questions answered from the telemetry surface and the context graph,
not from the resource’s control plane. (A pure “what breaks if <datastore> goes down”
blast-radius question is context-graph.md‘s.) Answering with the
methodology — the graph call, the signals to check, the verdict rubric — is an output
contract: surface every item below that applies, whether or not you also pull live
numbers and even if no matching service exists in the account yet. Presenting a verdict
without a telemetry check, or a dependency list without roles, is the single most common
wrong answer here.
GetContextGraph on the named service (nodeFilters.name, depth: 1,
includeMetadata: true, a window around the incident) before writing any query. Do
not open with a metric scan of the fleet or a depth: 3 sweep.CALLS
from <service>), a datastore (ACCESSES from <service> — database, queue,
cache), the host / compute it runs on (RUNS_ON from <service>), and its
callers (edges whose to is <service>). “3+ dependencies with correct roles” is
the bar; a bare list of names is not.ListAlerts / GetAlert) plus any live failing edge on the graph; for a resource,
the USE signals (utilization, saturation, a headroom signal, errors) plus alarm state.
Then state the verdict in one of exactly three words — HEALTHY, DEGRADED, or DOWN —
with the evidence behind it. A one-signal answer (“CPU is fine, it’s healthy”), or a
description of signals with no explicit verdict word, does not meet the bar.errorCode, an absent metric series,
or an empty node list.SERVICE node gets Rate / Errors / Duration; a RESOURCE
node (database, host, queue, cache) gets Utilization / Saturation / Errors / headroom.
Pick the shape from the node’s nodeType / nodeProperties.category, then — for a
specific resource type (EC2, RDS, ELB, …) — take the resource-specific signals and
their aggregation from the promql-metrics.md §5 per-service
catalog, not the generic USE list: e.g. an EC2 fleet needs StatusCheckFailed* read
with max (a 0/1 flag; averaging hides a failure), CPUCreditBalance with min
(burstable only), grouped by InstanceId — those specifics live in §5, not here.REMOTE_SERVICE node cannot be queried directly. It is an external dependency
with no telemetry of its own in this Space, so it gets neither RED nor USE — judge it
from the caller-side edge evidence (an errorCode / non-2xx httpStatusCode on the
edge into it, plus the calling service’s own spans), per the “a node you cannot query”
guidance in context-graph.md.metadata selectors, not from the graph’s display
name. With includeMetadata: true, metadata.metrics[] gives the metric names and
the raw attributes to match, metadata.traces[] / metadata.logs[] give the exact
column→value selectors, and alternateNames gives the raw service.name values a
merged node is stored under. Querying by the resolved display name misses a merged
node’s telemetry.<service>‘s own errors/latency
→ walk downstream (its dependencies). Who is affected if <service> fails → its
callers (upstream). State this before traversing. For the full transitive
blast-radius closure, defer to context-graph.md, which owns that
walk; here you only need the immediate callers to place their role and confirm their
health.GetAlert. Never reach a
metric with SQL over a metrics.default table (it does not exist), and never read a
verdict off the resource control plane (ecs describe-clusters,
dynamodb describe-table, ec2 describe-instance-status).The dependency-map mechanics, the verdict rubric, and how to assemble the checks are below.
A dependency map answers “what does <service> rely on, and how” — and the how is the
role. What is health-specific here is only the role each edge type assigns; the
traversal itself (inbound vs outbound edges, pagination, truncation) is
context-graph.md‘s — see its “Reading the graph” section. Roles come
straight from the edge type and direction (edges are directed from → to, and from
depends on to):
| Role | Edge, relative to <service> | Where it comes from |
|---|---|---|
| Caller (upstream) | edge whose to is <service> | the reverse query for inbound edges (edgeFilters.to) — a node’s own edges never include its callers |
| Callee service (downstream) | CALLS from <service> | <service>‘s own outbound edges, edgeType: CALLS |
| Datastore (downstream) | ACCESSES from <service> | <service>‘s own outbound edges, edgeType: ACCESSES — database, queue, cache |
| Host / compute (runs-on) | RUNS_ON from <service> | <service>‘s own outbound edges, edgeType: RUNS_ON |
Procedure:
GetContextGraph with nodeFilters.name = <service>, depth: 1,
includeMetadata: true, and a window around the question’s time (or a recent hour if
none was given).<service>‘s outbound edges by edgeType into the callee / datastore / runs-on
roles above, and fetch its callers with the reverse (inbound-edge) query — see
context-graph.md for the traversal mechanics.operations[], errorCode, httpStatusCode, lastObservedAt). An edge carrying an
errorCode or a non-2xx httpStatusCode is a proven failing dependency within the
window — follow that one first. An edge with no error fields is unreported, not
proven healthy.depth one hop at a time if the question asks for the transitive set, and
confirm the health of a suspect dependency with telemetry before expanding further.Two hops (depth: 2) is usually enough to answer “what does it depend on and what role
does each play” for a service and its immediate datastores. Go deeper only along a branch
that shows signal.
A pure blast-radius question (“what breaks if <datastore> goes down,” “who is affected
if <service> fails”) is owned by context-graph.md — it is the
reverse (upstream) traversal, and the mechanics (inbound-edge query, node-by-node
pagination, silent hub truncation, collect every page) all live there. Use it for that.
What is health-specific — and all you need here — is the read on an affected caller:
a caller whose edge to the target carries an errorCode or a non-2xx httpStatusCode
is proven-affected; a caller with an edge but no error fields is merely dependent, so
confirm its state with the caller’s own telemetry before calling it degraded.
A verdict rests on several signals, chosen by whether the node is a service or a resource.
The RED/USE shapes below are only the categories to cover — they are not the
per-resource signal list. For a specific resource type (EC2, RDS, ELB, DynamoDB, …), the
exact metrics, their aggregation, and the identity label to group by come from the
promql-metrics.md §5 per-service catalog — go there and
use its signals rather than the generic USE list here (which would under-cover a resource
type: an EC2 verdict needs StatusCheckFailed* with max, CPUCreditBalance with min
on burstable, memory/disk from the CloudWatch agent, grouped by InstanceId — §5 names
them, this section does not).
For a SERVICE node — RED:
traces.span.metrics.calls, a delta Sum —
sum_over_time(...[5m]), not rate()) or Application Signals request count.errors / total over the same window, never
a numerator alone. Application Signals Fault (5xx + span-status errors) and Error
(4xx), or the span error split on status['code'].traces.span.metrics.duration via histogram_quantile(0.99, rate(...[5m])),
Application Signals Latency, or span durationNano in SQL
(sql-logs-traces.md).For a RESOURCE node — USE:
CPUUtilization,
DatabaseConnections), read as a gauge (avg / max; on the enriched vended surface a
delta exponential histogram, so histogram_avg / histogram_quantile).DiskQueueDepth, message backlog / age, task counts.5xx, status-check failures (read a 0/1 check with max, never
averaged away).FreeStorageSpace, FreeableMemory, CPUCreditBalance (burstable only). A balance near
zero while utilization looks flat is throttling, not health.Across both — always add:
ListAlerts / GetAlert
(alerts.md). A WARNING or CRITICAL state, or a contributorSummary
count above zero, is direct evidence of a breaching signal; NODATA is itself a finding
(the rule ran and saw nothing), not “healthy”. OK is one signal, not the verdict.errorCode / non-2xx httpStatusCode on an
edge into or out of the node, with a lastObservedAt inside the window.State the verdict explicitly:
| Verdict | Shape of the evidence |
|---|---|
| HEALTHY | Error rate at baseline, p99 latency within norms, no saturation/headroom signal near a limit, alarms OK, no failing edges in the window — and at least one of these actually queried, not assumed. |
| DEGRADED | One or more signals elevated but the service is still serving: raised error rate or latency, a headroom signal trending toward a limit, an alarm at WARNING, or a proven failing edge to one dependency while others are clean. Name which signal and the number. |
| DOWN | Serving is broken: request rate collapsed to ~0 with errors, alarm CRITICAL, or the node/its critical datastore unreachable. Distinguish “the service is the cause” from “a dependency is down and this service is a victim” using the downstream walk. |
Every verdict cites the evidence: the metric and its value, the span/log query, the alarm state, and the edge. A verdict with no cited signal is the failure this file exists to prevent.
The metadata block (only with includeMetadata: true) is what makes the queries match
what is actually stored, rather than the graph’s normalized name:
metadata.metrics[] gives each metric name, its preferredStat, and
the raw attributes (service.name, service.namespace, cloud.region, …) to use
as PromQL label matchers. Build the selector per
promql-metrics.md — do not guess the metric name from the
node’s display name.metadata.traces[] / metadata.logs[] are per-signal query
selectors (OR’ed blocks of AND’ed exact column→value maps). Use them and
alternateNames to write the SQL over traces.default / logs.default per
sql-logs-traces.md, always with a `@timestamp` range.alternateNames) is what you match when listing
its alerts (alerts.md).Read the account and Region for every follow-up query off the node
(nodeProperties.region, nodeProperties.sourceAccountId) — do not assume they match the
credentials you signed with, because a Space can centralize telemetry forwarded from other
accounts.
NODATA on an alert is a finding, not health. The rule evaluated and saw nothing —
investigate why (no telemetry reaching the Space, wrong scope) rather than reading it as
a pass.errorCode, a metric with no series, and an
alert at OK each mean “this one signal did not report a problem”, not “the service is
healthy”. The verdict comes from the set of signals.ecs describe-clusters,
dynamodb describe-table, ec2 describe-instance-status, and list-metrics describe
configuration, not health. Health is on the telemetry surface.GetContextGraph, never X-Ray GetServiceGraph. If a too-old AWS CLI
rejects the cloudwatchomni command (invalid choice), or boto3/botocore raises
UnknownServiceError for the cloudwatch-omni service, that is a client-version issue
(upgrade the CLI/SDK), not a reason to switch products.