Setting the file. One moment. Di Status Tools · AWS Observability · aws/agent-toolkit-for-aws · Skills Docs69
Creating Amazon Aurora Db Cluster With Instances
92
Routing Traffic With Route53 And CloudFront
Resilience Program Design
Debugging Lambda Timeouts
Next
Script Di Validation
scripts/cloudwatch/di_status_tools.py
Python·359 lines·13 KB
import
assess
11from di_status_rendering import (
12 render_get_instrumentation_configuration_status_output,
13 render_status_assessment,
14)
15from di_validation import (
16 is_valid_location_hash,
17 normalize_instrumentation_type,
18 validate_snapshot_signal,
19)
20
21
22def _check_status_with_time_range(
23 *,
24 service: str,
25 environment: str,
26 instrumentation_type: str,
27 location_identifier: Dict[str, Any],
28 status: str,
29 start_time: datetime,
30 end_time: datetime,
31 signal_type: str = SNAPSHOT_SIGNAL_TYPE,
32) -> Tuple[bool, List[dict], Optional[str]]:
33 """Check whether status events exist for the configuration in a time range."""
34 try:
35 data = gateway.get_instrumentation_configuration_status(
36 InstrumentationType=instrumentation_type,
37 Service=service,
38 Environment=environment,
39 SignalType=signal_type,
40 Status=status,
41 LocationIdentifier=location_identifier,
42 StartTime=start_time,
43 EndTime=end_time,
44 )
45 except gateway.GatewayError as err:
46 return False, [], f"API error: {err.original_exc}"
47
48 events = data.get("Events", []) if isinstance(data, dict) else []
49 return len(events) > 0, events, None
50
51
52def _render_status_identifier_help() -> str:
53 return """ERROR: Must provide one of:
54- location_hash
55- language + file_path (for code location identifier)
56
57Usage:
581. Get by hash (preferred):
59 get_instrumentation_configuration_status(location_hash="abc123...")
60
612. Get by code location:
62 get_instrumentation_configuration_status(language="Python", file_path="/app/file.py", ...)"""
63
64
65def _parse_iso_timestamp(value: str) -> datetime:
66 """Parse an ISO 8601 timestamp, accepting trailing 'Z' as UTC.
67
68 A naive input (no 'Z' or offset, e.g. ``2025-02-03T18:42:00``) is assumed
69 to be UTC rather than host-local. Without this, downstream ``astimezone``
70 calls in ``assess()`` would reinterpret it in the host timezone — on a
71 UTC-8 host ``18:42`` becomes ``02:42Z``, shifting the whole status query
72 window and causing ACTIVE/READY events to be missed.
73 """
74 parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
75 if parsed.tzinfo is None:
76 parsed = parsed.replace(tzinfo=timezone.utc)
77 return parsed
78
79
80def get_instrumentation_configuration_status(
81 service: str,
82 environment: str,
83 instrumentation_type: str,
84 location_hash: Optional[str] = None,
85 language: Optional[str] = None,
86 file_path: Optional[str] = None,
87 code_unit: Optional[str] = None,
88 class_name: Optional[str] = None,
89 method_name: Optional[str] = None,
90 line_number: Optional[int] = None,
91 status: Optional[str] = None,
92 start_time: Optional[str] = None,
93 end_time: Optional[str] = None,
94 max_results: int = 100,
95 next_token: Optional[str] = None,
96 signal_type: str = SNAPSHOT_SIGNAL_TYPE,
97) -> OpResult:
98 """Get status-event history for one instrumentation configuration and one explicit status.
99
100 This API is intentionally strict: callers must provide exactly one status
101 filter because AWS defaults can be ambiguous. The response distinguishes
102 between the backend's current status field and status confirmation based on
103 returned events.
104
105 Args:
106 service: Backend service identifier.
107 environment: Backend environment identifier.
108 instrumentation_type: BREAKPOINT or PROBE.
109 location_hash: Preferred identifier for an existing configuration.
110 language: Code language for code-location lookup.
111 file_path: Code file path for code-location lookup.
112 code_unit: Optional module/package name for code-location lookup.
113 class_name: Optional class name for code-location lookup.
114 method_name: Optional function/method name for code-location lookup.
115 line_number: Optional 1-based line number for code-location lookup.
116 status: Required. Must be READY, ACTIVE, ERROR, or DISABLED.
117 start_time: Optional ISO 8601 lower bound for returned events.
118 end_time: Optional ISO 8601 upper bound for returned events.
119 max_results: Maximum number of events to request. Defaults to 100.
120 next_token: Optional AWS pagination token from a previous response.
121 signal_type: Must be SNAPSHOT.
122
123 Returns:
124 A human-readable status report with location details, event count,
125 confirmation guidance, and pagination hints when additional events exist.
126 """
127 normalized_type, type_error = normalize_instrumentation_type(instrumentation_type)
128 if type_error:
129 return OpResult(False, type_error)
130 signal_error = validate_snapshot_signal(signal_type)
131 if signal_error:
132 return OpResult(False, signal_error)
133
134 location, location_error = parse_lookup_inputs(
135 normalized_type=normalized_type,
136 location_hash=location_hash,
137 language=language,
138 file_path=file_path,
139 code_unit=code_unit,
140 class_name=class_name,
141 method_name=method_name,
142 line_number=line_number,
143 allow_code_location_lookup=True,
144 )
145 if location_error:
146 if "missing location identifier input" in location_error:
147 return OpResult(False, _render_status_identifier_help())
148 return OpResult(False, f"ERROR: {location_error}")
149 if location is None:
150 # Defensive: parsers return (loc, None) or (None, error_text). This
151 # branch should be unreachable, but we return a user-facing error
152 # string (not ``raise``) so the tool's "always returns a string"
153 # contract holds even if a future parser bug fires this path.
154 return OpResult(
155 False, "ERROR: Internal error resolving location. Please report this issue."
156 )
157 target_desc = location.describe()
158
159 requested_status = (status or "").strip().upper()
160 allowed_statuses = {"READY", "ACTIVE", "ERROR", "DISABLED"}
161 if not requested_status:
162 return OpResult(
163 False,
164 """ERROR: status is required
165
166This API cannot return all statuses in one call.
167If status is omitted, AWS defaults to ACTIVE, which is ambiguous.
168
169Use explicit status checks in this order:
1701. status="READY"
1712. status="ACTIVE" (only after READY is confirmed by events)
1723. status="ERROR" (if READY not confirmed)
1734. status="DISABLED" (when checking max-hits scenarios)""",
174 )
175
176 if requested_status not in allowed_statuses:
177 return OpResult(
178 False,
179 "ERROR: invalid status. Must be one of: READY, ACTIVE, ERROR, DISABLED "
180 f"(received: {status})",
181 )
182
183 request_kwargs: Dict[str, Any] = {
184 "InstrumentationType": normalized_type,
185 "Service": service,
186 "Environment": environment,
187 "SignalType": SNAPSHOT_SIGNAL_TYPE,
188 "Status": requested_status,
189 "LocationIdentifier": location.to_identifier(),
190 }
191
192 if start_time:
193 try:
194 request_kwargs["StartTime"] = _parse_iso_timestamp(start_time)
195 except ValueError as exc:
196 return OpResult(
197 False, f"ERROR: Invalid start_time format. Expected ISO 8601. Error: {exc}"
198 )
199 if end_time:
200 try:
201 request_kwargs["EndTime"] = _parse_iso_timestamp(end_time)
202 except ValueError as exc:
203 return OpResult(
204 False, f"ERROR: Invalid end_time format. Expected ISO 8601. Error: {exc}"
205 )
206 if max_results != 100:
207 request_kwargs["MaxResults"] = max_results
208 if next_token:
209 request_kwargs["NextToken"] = next_token
210
211 try:
212 data = gateway.get_instrumentation_configuration_status(**request_kwargs)
213 except gateway.GatewayError as err:
214 return OpResult(
215 False,
216 gateway.render_error(
217 err,
218 action="get instrumentation status",
219 attempted_label="ATTEMPTED TO RETRIEVE:",
220 attempted={
221 "Target": target_desc,
222 "Service": service,
223 "Environment": environment,
224 },
225 possible_causes=[
226 "Instrumentation doesn't exist at this location",
227 "Location parameters don't match exactly",
228 "Wrong service or environment identifier",
229 ],
230 troubleshooting=["Use get_instrumentation to verify the configuration exists"],
231 ),
232 )
233
234 return OpResult(
235 True,
236 render_get_instrumentation_configuration_status_output(
237 data=data,
238 normalized_type=normalized_type,
239 service=service,
240 environment=environment,
241 requested_status=requested_status,
242 ),
243 )
244
245
246def check_instrumentation_status(
247 service: str,
248 environment: str,
249 instrumentation_type: str,
250 location_hash: str,
251 start_time: str,
252 end_time: str,
253 signal_type: str = SNAPSHOT_SIGNAL_TYPE,
254) -> OpResult:
255 """Run a consolidated READY/ACTIVE/ERROR status check over a time window.
256
257 This helper is opinionated: it first fetches the instrumentation creation
258 time, clamps the ACTIVE search window so it does not start before creation,
259 and then checks ACTIVE, READY, and ERROR in order to produce a single
260 high-level interpretation.
261
262 Args:
263 service: Backend service identifier.
264 environment: Backend environment identifier.
265 instrumentation_type: BREAKPOINT or PROBE.
266 location_hash: Required 16-character lowercase hex location hash for the target configuration.
267 start_time: Required ISO 8601 lower bound for the overall check window.
268 end_time: Required ISO 8601 upper bound for the overall check window.
269 signal_type: Must be SNAPSHOT.
270
271 Returns:
272 A human-readable consolidated assessment such as ACTIVE, READY, ERROR, or
273 PENDING, plus troubleshooting guidance and snapshot-query hints when applicable.
274 """
275 normalized_type, type_error = normalize_instrumentation_type(instrumentation_type)
276 if type_error:
277 return OpResult(False, type_error)
278 signal_error = validate_snapshot_signal(signal_type)
279 if signal_error:
280 return OpResult(False, signal_error)
281
282 if not is_valid_location_hash(location_hash):
283 return OpResult(False, "ERROR: location_hash must be a 16-character hex string")
284
285 try:
286 created_at_response = gateway.get_instrumentation_configuration(
287 InstrumentationType=normalized_type,
288 Service=service,
289 Environment=environment,
290 SignalType=SNAPSHOT_SIGNAL_TYPE,
291 LocationIdentifier={"LocationHash": location_hash},
292 )
293 except gateway.GatewayError as err:
294 return OpResult(False, f"ERROR: Failed to fetch created_at: Exception: {err.original_exc}")
295
296 config = (
297 created_at_response.get("Configuration", {})
298 if isinstance(created_at_response, dict)
299 else {}
300 )
301 if not config:
302 return OpResult(
303 False,
304 f"ERROR: Failed to fetch created_at: No instrumentation found for LocationHash {location_hash}",
305 )
306 created_dt = config.get("CreatedAt")
307 if created_dt is None:
308 return OpResult(
309 False,
310 "ERROR: Failed to fetch created_at: CreatedAt not found in instrumentation configuration",
311 )
312
313 try:
314 start_dt = _parse_iso_timestamp(start_time)
315 except ValueError as exc:
316 return OpResult(False, f"ERROR: Invalid start_time format. Expected ISO 8601. Error: {exc}")
317
318 try:
319 query_end_dt = _parse_iso_timestamp(end_time)
320 except ValueError as exc:
321 return OpResult(False, f"ERROR: Invalid end_time format. Expected ISO 8601. Error: {exc}")
322
323 if query_end_dt <= start_dt:
324 return OpResult(False, "ERROR: end_time must be later than start_time")
325
326 location_identifier = {"LocationHash": location_hash}
327
328 def check_status(
329 status: str, start: datetime, end: datetime
330 ) -> Tuple[bool, List[dict], Optional[str]]:
331 return _check_status_with_time_range(
332 service=service,
333 environment=environment,
334 instrumentation_type=normalized_type,
335 location_identifier=location_identifier,
336 status=status,
337 start_time=start,
338 end_time=end,
339 signal_type=SNAPSHOT_SIGNAL_TYPE,
340 )
341
342 verdict, time_window = assess(
343 created_at=created_dt,
344 requested_start=start_dt,
345 query_end=query_end_dt,
346 check_status=check_status,
347 )
348
349 return OpResult(
350 True,
351 render_status_assessment(
352 verdict,
353 location_hash=location_hash,
354 service=service,
355 environment=environment,
356 normalized_type=normalized_type,
357 time_window=time_window,
358 ),
359 )