Observability Csp Investigation
ElasticOfficialCreated by elastic
- Skill
- Review and audit
- Cloud and DevOps
- Apache-2.0
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured, provider-agnostic methodology for investigating cloud-resource anomalies. It guides users through resolving resource telemetry, quantifying incident-window metrics against representative baselines, and classifying failure modes using specific signatures. Designed for use with the Elastic CLI and ES|QL, it emphasizes evidence-based diagnosis, the importance of calculating rates over raw counts, and the necessity of verifying dependencies before attributing cross-service causation.
What it does
- Provides a standardized workflow for investigating cloud-provider service issues.
- Guides the comparison of incident-window metrics against representative baselines.
- Defines how to compute meaningful rates from raw counters to distinguish load from failure.
- Instructs on how to use logs to identify the root cause of an anomaly.
- Offers a framework for ruling out unrelated co-occurring incidents.
- Maps common cloud-provider metrics to specific failure-mode signatures.
When to use it
- When diagnosing cloud-resource problems triggered by alerts or ad-hoc investigations.
- When investigating database connection, CPU, or memory pressure.
- When analyzing function error rates, throttling, or queue backlog issues.
- When troubleshooting load-balancer 5xx errors or instance saturation.
- When you need to verify if an alert represents a genuine system failure or ambient fluctuation.
Advantages
- Reduces misattribution by enforcing dependency testing before linking incidents.
- Prevents false positives by comparing metrics against resource-specific baselines.
- Provides a clear, repeatable process for complex cloud investigations.
- Ensures consistent interpretation of OTel-schema metrics across different cloud services.
- Helps distinguish between genuine failures and expected ambient error rates.
Usage examples
- “The RDS connections alert fired on orders-db — what's wrong?”
- The agent will guide you to compare current DatabaseConnections against a prior-day baseline, check for plateauing, and inspect sibling instances to determine if the issue is isolated or platform-wide.
- “Lambda order-worker error rate jumped, and the database is alerting at the same time.”
- The agent will help you calculate the error rate, verify if the function actually depends on the database, and check logs to see if the failure is in the function's code or the database call.
- “Alert fired on my EC2 instance but everything looks healthy.”
- The agent will walk you through comparing incident-window metrics to the baseline to confirm if the alert is spurious or resolved, ultimately providing a healthy verdict.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/elastic/observability-csp-investigation/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip observability-csp-investigation.zip -d .agents/skills
Documentation by elastic
Cloud Provider (CSP) Investigation
Diagnose cloud-provider service issues from OTel-schema metrics and logs in Elastic. The guidelines, flow, and synthesis below are provider-agnostic and apply to every investigation. The per-provider reference carries the schema facts and failure-mode signatures that cannot be guessed — read it first.
This is the CSP domain layer: which index holds a resource's telemetry, what shape its metrics take, and how to read them into a diagnosis. It assumes the harness can already author ES|QL — it does not teach query syntax, null handling, or function usage. Keep that split: this skill tells you what to query and how to interpret it; writing the query itself is the harness's job.
<!-- begin-partial: preamble -->
Environment Configuration
This skill executes Elasticsearch operations through the elastic CLI. If the
elastic CLI is not installed, tell the user what it is needed for. Do
not guess credentials, call the HTTP API directly, or attempt other workarounds.
This skill references operations in HTTP-shorthand form (e.g., GET /, GET /_cat/indices, GET /{index}/_mapping,
GET /{index}/_settings/index.mode, POST /_query). The Operations table at the end of this document
maps each shorthand to the equivalent elastic CLI command — always use the CLI rather than calling the HTTP API
directly.
<!-- end-partial: preamble -->
Analysis without cluster access
The CLI check above gates querying the cluster — it does not gate analysis. When the user has already supplied the evidence in their question (metric values, alert payloads, log lines, dimension names), reason from that evidence and deliver the conclusion.
When you genuinely do need data the user has not provided, still say what you would check and how — name the specific query, index, and field that would settle the question — and then ask for CLI setup. An answer that names the check is useful without a cluster; one that only asks for setup is not.
Every ES|QL query in this skill and in the provider references runs via POST /_query. Discovering which data streams
and fields a deployment actually has uses GET /_resolve/index/<pattern> and GET /_field_caps (the way to learn
whether a sparse counter has ever been mapped — see Guidelines). Active alerts are read with
GET kbn:/api/alerting/rules/_find; shipped SLOs with GET kbn:/api/observability/slos. The Operations
table maps each to its elastic CLI equivalent. Express predicates natively in ES|QL; do not use Query DSL or the KQL
function.
Router — read the provider reference first
Identify the provider from the resource under investigation, then read its reference before writing any query (the index patterns, field shapes, and stat/aggregation rules there are unguessable, and a query written without them fails or silently corrupts its aggregates):
| Provider | Resource types | Reference |
|---|---|---|
| AWS | Any service under metrics-aws.*; failure signatures documented for RDS, Lambda, SQS, ALB/ELB, EC2 | references/aws.md |
| GCP, Azure | — | not yet covered (see below) |
If the provider is unclear, determine it from the data before assuming: GET /_resolve/index/metrics-* lists the data
streams that exist, and the provider is in their names. For a provider without a reference yet, apply the
provider-agnostic guidance below, discover the schema empirically (list data streams → field mapping → probe query), cap
confidence at medium, and say the reference was unavailable — never transplant another provider's schema.
The same applies within a provider. For an AWS service the reference doesn't detail (DynamoDB, ECS, API Gateway,
Kinesis, ElastiCache, …), the general guidance and the AWS-wide conventions still hold — the metrics-aws.* index
family, backticked dotted/slashed field names, and per-attributes.stat filtering — so apply those, discover that
service's specific metrics empirically, and cap confidence where no documented failure-mode signature exists. The
guidelines below are service-agnostic by design; only the tuned signatures are per-service.
Guidelines
Investigate the named resource before anything else — the one the alert fired on, or the one the request names. Shared clusters carry many systems' telemetry (Kubernetes workloads, other teams' accounts, demo apps). Signals from co-resident systems are not evidence about this alert unless a dependency between them is demonstrated. Do not promote a louder co-resident anomaly to root cause.
The alert's own metric, compared to its own baseline, outranks everything else. Before considering any other metric, quantify the alerting metric's incident-window value against a representative window of the same resource's own normal (how to choose that window — trailing vs same-time-prior-day — is in Flow below; the point is it must be representative, not merely adjacent). A 10×+ delta on the alert's metric is the primary thread; small wiggles in other metrics are secondary until the primary thread is exhausted. "High" is only meaningful relative to this resource's normal.
Counters up ≠ failing harder. Always compute the rate. For error counters, divide by the volume counter (errors/invocations, 5xx/requests) in BOTH windows. Volume up with rate flat = load change, not a failure. Rate up with volume flat = a real failure in the resource's own code/config path.
One instance degrading while its fleet siblings stay healthy = the cause is scoped to that instance. Check the same metric on sibling resources. Healthy siblings rule out platform-wide issues, shared event sources, and shared downstream dependencies — the fault is in the affected instance's own code, configuration, or deployment.
Co-occurring incidents are not causally linked by timing alone. Attribute cross-service causation only when (a) a dependency actually exists (the service calls that resource), and (b) the upstream degradation preceded the downstream symptom. Two resources alerting in the same window on a busy cluster is the norm, not a clue. When you notice another resource anomalous in the same window, TEST the dependency before linking: does the affected resource actually call it? A worker that only touches a queue cannot be broken by a database incident. If you cannot demonstrate the dependency from the data, report the other incident as concurrent-and-unrelated and keep diagnosing the named resource on its own signals.
A healthy verdict is a first-class outcome, not a failure to find the problem. Many workloads run with an ambient
error rate of several percent at all times. If the named resource's incident-window numbers are indistinguishable from
its baseline (rate ratio near 1×; volume, duration, throttles, backlog at baseline), the correct conclusion is that the
alert is spurious or already resolved (or, for an ad-hoc check with no alert, that there is no problem): state it
plainly — "ALERT FIRED BUT SYSTEM APPEARS HEALTHY", or "NO PROBLEM FOUND — <resource> IS AT BASELINE" — show the
incident-vs-baseline numbers, and stop. Constructing a root-cause story out of ambient fluctuation is a worse failure
than reporting no incident.
Name exactly one primary cause; everything else is an effect, a contributor, or unrelated. When one resource saturates (e.g. CPU pinned at 100%), the metrics it drives rise with it — IO, latency, queue depth. Those are downstream effects, not co-equal causes. A conclusion naming two "co-primary" bottlenecks is usually an unfinished diagnosis: determine which one explains the other and say so.
Logs carry the "why" that metrics cannot — cite them when available. Metrics localize which component failed and
rule failure modes in or out; the specific cause (the exception, the failing query, the bad config) lives in the
resource's logs. After classifying from metrics, check the implicated resource's logs and cite the concrete error — turn
"the function's code path is failing" into "RuntimeError at index.py:11". Logs arrive in one of two shapes and you
should check both: OTel-native logs-*.otel-* (filter by service.name, line in body.text) and the classic
cloud-integration shape (e.g. AWS logs-aws_logs.*, line in the message field). Zero rows in one shape is not "no
errors" — the logs may be in the other, or not shipped at all; say which you checked. Reading a log line does not excuse
misattribution: if a function logs its own exception, that is the cause, even when another resource is alerting in the
same window.
Absence of a metric field is data. Cloud providers omit zero-valued sparse counters (error counts, per-code 5xx,
throttle counts), so the field may be unmapped in Elastic until the first such event ever occurs — and ES|QL fails the
whole query on an unknown column. Check the field with GET /_field_caps before referencing it, or query sparse fields
in separate small queries; a verification failure or empty result on a sparse counter means "no such events ever
recorded," never "data missing."
Report insufficient evidence rather than manufacturing a conclusion. If the named resource has zero datapoints in the window, say so, list the exact index patterns and filters you tried, and stop. Recent windows may simply not be ingested yet (cloud-metric pipelines typically lag by minutes — the provider reference gives the number).
Flow
Orient: resolve the resource and the two windows. The incident window brackets the time of interest — the alert time, or the reported symptom time. The baseline is a representative span of the same resource's normal, chosen to match its rhythm: a short trailing window (~30–60m before the incident) is fine for a steady workload, but for anything with daily or weekly seasonality widen the window or compare against the same time-of-day on a prior comparable day — a half-hour adjacent baseline makes normal diurnal variation read as the incident. Windows must not overlap. When the resource's rhythm is unknown, look back far enough to see at least one full normal cycle before trusting "high". Decision: which data stream, which dimension value, which two time ranges every later query uses. Data:
GET /_resolve/index/<pattern>for the streams that exist; the alert payload or request for the resource name.Quantify the alert's own metric against its baseline. One
POST /_queryper window (or one query with a per-windowCASE), using the stat filter and backticked field names from the provider reference. Decision: is the delta material (roughly ≥2× the resource's own baseline rate, or 10×+ on an absolute metric), or is the resource at baseline? If at baseline, go to step 5 with a healthy verdict.Classify against the provider reference's failure-mode signatures. Pull the discriminating metrics the signature table names (the ceiling plateau, the throttle counter, the deleted-vs-sent ratio, the target-vs-ELB 5xx split) and compute the rates. Decision: exactly one named mode, or "no documented signature matches" with confidence capped at medium.
Corroborate. Siblings (
POST /_queryfor the same metric across the fleet); dependent or upstream resources only where a real dependency exists; the implicated resource's logs in both shapes; shipped SLOs (GET kbn:/api/observability/slos) and active alerting rules (GET kbn:/api/alerting/rules/_find, filtering client-side for rules whose params name this resource) to anchor severity. Decision: does anything contradict the classification? Anchor severity on a VIOLATED SLO or an active alert when present; never invent one.Synthesize and stop. Terminate as soon as the evidence supports a classification at known confidence; exhaustive exploration is a failure mode.
Synthesis
State: root cause (one sentence, named mode + resource), evidence with concrete numbers (incident vs baseline), causal chain, scope (which resources affected, which ruled out and why), recommended action targeting the actual mode, and confidence. Downgrade confidence when a discriminating signal was unavailable and say which. "ALERT FIRED BUT THE SYSTEM APPEARS HEALTHY" (or "no problem found" for an ad-hoc check) and "insufficient evidence for the named resource" are valid, complete conclusions when the data supports them.
Examples
"The RDS connections alert fired on orders-db — what's wrong?"
Orient on metrics-aws.rds.otel-* filtered by attributes.DBInstanceIdentifier == "orders-db", incident window around
the alert, baseline the same hour on the prior day (databases are diurnal). Quantify DatabaseConnections (stat
Maximum) in both windows. If it climbed far above baseline and plateaued while CPU, memory, storage and IO stayed
nominal, that is the connection-exhaustion signature — RDS emits no max_connections, so infer the ceiling from the
plateau. Corroborate on sibling instances (healthy siblings scope the cause to this instance's callers), then pull the
callers' logs for the concrete error (too many connections, pool timeouts). Recommend the fix for the mode: raise the
ceiling or shrink the callers' pools, not a restart.
"Lambda order-worker error rate jumped, and the database is alerting at the same time"
Compute the rate, not the count: Errors / Invocations (stat Sum) in both windows. If the rate rose at flat volume,
the function's own code path is failing. Before linking it to the database incident, test the dependency: a queue
consumer that never calls the database cannot be broken by it. Pull the function's own logs (logs-aws_logs.* or
logs-*.otel-*) and cite the exception. Report the database incident as concurrent-and-unrelated unless the logs show
the function calling it and failing on that call. If volume rose and the rate did not, it is a load surge — nothing is
broken.
"Alert fired but everything looks healthy"
A first-class outcome. Show the incident-vs-baseline numbers for the alerting metric and its volume counter; if the
ratio is near 1× and the discriminating metrics are at baseline, state ALERT FIRED BUT SYSTEM APPEARS HEALTHY, list
what you checked, and recommend tuning the rule (window, threshold relative to the resource's own normal) rather than
constructing a cause from ambient fluctuation.
Operations
| HTTP API (shorthand) | elastic CLI command |
|---|---|
GET / | elastic es info |
POST /_query | elastic es esql query --format tsv --query '<esql>' |
GET /_resolve/index/<pattern> | elastic es indices resolve-index --name '<pattern>' |
GET /<index>/_mapping | elastic es indices get-mapping --index '<index>' |
GET /_field_caps | elastic es field-caps --index '<index>' --fields '<fields>' |
GET kbn:/api/alerting/rules/_find | elastic kb alerting get-alerting-rules-find --filter '<filter>' |
GET kbn:/api/observability/slos | elastic kb slo find-slos-op --space-id '<space>' --kql-query '<kql>' |
Files
- ATTRIBUTION.mdAdded by AutoGineer988 B
- LICENSELicence file added by AutoGineer11.1 KiB
- SKILL.md16.8 KiB
- references/aws.md7.1 KiB
references/aws.md
AWS reference (OTel schema via EDOT awscloudwatch)
Provider-specific facts for AWS investigations. The investigation doctrine lives in the parent SKILL.md — this file is
the schema and the failure-mode signatures. Every query here is ES|QL and runs via POST /_query; confirm which data
streams a deployment actually has with GET /_resolve/index/metrics-aws.*.otel-* before assuming a service is covered.
In scope: metrics-aws.*.otel-* data streams (OTel semconv AWS CloudWatch shape). Out of scope: the legacy
Elastic AWS integration shape (metrics-aws.* without .otel, aws.* ECS fields) — do not author queries against it.
Schema (unguessable — use exactly this)
Logs (when shipped): the classic AWS-integration shape is logs-aws_logs.* (per-service datasets, e.g.
logs-aws_logs.lambda_fis-*; the log line is the message field, ECS-shaped). The OTel shape is logs-*.otel-*
(filter by service.name; line in body.text). A given environment may have one, both, or neither.
Data streams, one per service: metrics-aws.rds.otel-*, metrics-aws.lambda.otel-*, metrics-aws.sqs.otel-*,
metrics-aws.elb.otel-*, metrics-aws.ec2.otel-*.
Metric value fields are the CloudWatch names flattened under metrics. with dots and slashes — they MUST be
backticked in ES|QL: `metrics.amazonaws.com/AWS/RDS/DatabaseConnections`,
`metrics.amazonaws.com/AWS/Lambda/Errors`,
`metrics.amazonaws.com/AWS/SQS/ApproximateNumberOfMessagesVisible`,
`metrics.amazonaws.com/AWS/ApplicationELB/HTTPCode_Target_5XX_Count`.
Filter rows by CloudWatch dimensions as attributes.<Dimension>: attributes.DBInstanceIdentifier,
attributes.FunctionName, attributes.QueueName, attributes.LoadBalancer (full app/<name>/<id> form),
attributes.TargetGroup, attributes.InstanceId, plus attributes.MetricName and attributes.Namespace.
Every metric arrives as up to three parallel stat streams distinguished by attributes.stat (Average | Sum |
Maximum). Aggregating without a stat filter mixes the streams and corrupts the number (error counts come out
fractional). Use a per-aggregation filter and pick the stat that matches the semantic:
FROM metrics-aws.lambda.otel-*
| WHERE attributes.FunctionName == "<fn>" AND @timestamp >= <start> AND @timestamp <= <end>
| STATS errors = SUM(`metrics.amazonaws.com/AWS/Lambda/Errors`) WHERE attributes.stat == "Sum",
invocations = SUM(`metrics.amazonaws.com/AWS/Lambda/Invocations`) WHERE attributes.stat == "Sum",
p_duration = MAX(`metrics.amazonaws.com/AWS/Lambda/Duration`) WHERE attributes.stat == "Maximum"
| EVAL error_rate = CASE(invocations > 0, TO_DOUBLE(errors) / invocations, null)Counters (Errors, RequestCount, 5xx counts, MessagesSent/Deleted) → Sum. Peaks (Duration, ConcurrentExecutions,
connection peaks, backlog peaks) → Maximum. Levels (FreeableMemory, CPUUtilization averages) → Average. Guard every
ratio with CASE(denominator > 0, …, null) — a null rate means zero volume, not 0%.
Sparse counters are unmapped until their first non-zero event. Errors, Throttles, per-code
HTTPCode_*_5XX_Count and similar only exist as fields once such an event has been recorded, and ES|QL fails the whole
query on an unknown column. Check with GET /_field_caps (or a separate small query) before adding them to a larger
statement.
Ingest lag: CloudWatch → Elastic is typically ~4 minutes (collector delay + scrape interval). A window ending at "now" has an unpopulated tail.
Failure-mode signatures
| Resource | Mode | Pivotal signature |
|---|---|---|
| RDS | connection exhaustion | DatabaseConnections climbs far above baseline and plateaus at a ceiling while CPU/memory/storage/IO stay nominal (RDS emits no max_connections — infer the ceiling from the plateau) |
| RDS | cpu / memory / io / storage | the corresponding resource metric is the outlier; connections near baseline |
| Lambda | throttling | Throttles > 0, invocations rejected; concurrency pinned |
| Lambda | elevated error rate | error rate materially above ITS OWN baseline rate at comparable volume → the function's code/dependency; check sibling functions to confirm scope |
| Lambda | load surge | invocations ≫ baseline with rate ≈ baseline — absolute errors rise, nothing is broken |
| Lambda | duration degradation | rate ≈ baseline, Duration materially up (≥1.5×) |
| SQS | consumer lag | backlog + oldest-age climb; deleted > 0 but < sent |
| SQS | consumer stalled | backlog climbs; deleted ≈ 0 |
| SQS | producer spike | sent ≫ baseline and exceeds deleted |
| ALB | target vs ELB 5xx | Target_5XX = the backend answered errors; ELB per-code: 502 can't keep target connections, 503 no healthy target/capacity, 504 target timeout, 500 ALB internal |
| ALB | no healthy targets | ELB 503 storm while host-count metrics go ABSENT (deregistered targets emit no HealthyHostCount datapoints at all — scale-to-zero removes the metric rather than reading 0) |
Lambda co-symptom trap (co-locate this with the guideline): a worker/queue-consumer function does not call the application database. If a Lambda's error rate jumps while a database or other resource is also alerting in the same window, that other incident is a co-symptom of shared load or coincidence — NOT the cause — unless the function demonstrably calls it. Name the failure in the function's own code path; do not attribute it to the concurrent resource without a proven dependency.
Ambient error rates are normal (many real workloads idle at several percent). Materiality bar: roughly ≥2× the resource's own baseline rate, or a shift of many percentage points — not a drift of one or two.
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
The author says: Requires the `elastic` CLI (>= 0.2) with an Elasticsearch context, and cloud-provider metrics ingested through EDOT (the OpenTelemetry `awscloudwatch` receiver) into `metrics-aws.<service>.otel-*` data streams. The base floor is Elasticsearch 8.11 or later, or Serverless; every query is ES|QL. Alert-state and SLO lookups additionally need a Kibana context on the same deployment. Read-only.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- elastic
- Publisher
- Elastic (Official)
- Source
- elastic/agent-skills · skills/observability/csp-investigation · commit 09d879d
- Licence
- Apache License 2.0 (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
- Also distributed in
- elastic/agent-skills
AutoGineer is not affiliated with or endorsed by elastic or Elastic.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download observability-csp-investigation.zip