holmesgpt 0.13.3a0__py3-none-any.whl → 0.14.0a0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of holmesgpt might be problematic. Click here for more details.

@@ -0,0 +1,473 @@
1
+ """Grafana Tempo API wrapper for querying traces and metrics."""
2
+
3
+ import logging
4
+ from typing import Any, Dict, Optional, Union
5
+ from urllib.parse import quote
6
+
7
+ import backoff
8
+ import requests # type: ignore
9
+
10
+ from holmes.plugins.toolsets.grafana.common import (
11
+ GrafanaTempoConfig,
12
+ build_headers,
13
+ get_base_url,
14
+ )
15
+
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class TempoAPIError(Exception):
21
+ """Custom exception for Tempo API errors with detailed response information."""
22
+
23
+ def __init__(self, status_code: int, response_text: str, url: str):
24
+ self.status_code = status_code
25
+ self.response_text = response_text
26
+ self.url = url
27
+
28
+ # Try to extract error message from JSON response
29
+ try:
30
+ import json
31
+
32
+ error_data = json.loads(response_text)
33
+ # Tempo may return errors in different formats
34
+ error_message = (
35
+ error_data.get("error")
36
+ or error_data.get("message")
37
+ or error_data.get("errorType")
38
+ or response_text
39
+ )
40
+ except (json.JSONDecodeError, TypeError):
41
+ error_message = response_text
42
+
43
+ super().__init__(f"Tempo API error {status_code}: {error_message}")
44
+
45
+
46
+ class GrafanaTempoAPI:
47
+ """Python wrapper for Grafana Tempo REST API.
48
+
49
+ This class provides a clean interface to all Tempo API endpoints,
50
+ supporting both GET and POST methods based on configuration.
51
+ """
52
+
53
+ def __init__(self, config: GrafanaTempoConfig, use_post: bool = False):
54
+ """Initialize the Tempo API wrapper.
55
+
56
+ Args:
57
+ config: GrafanaTempoConfig instance with connection details
58
+ use_post: If True, use POST method for API calls. Defaults to False (GET).
59
+ """
60
+ self.config = config
61
+ self.base_url = get_base_url(config)
62
+ self.headers = build_headers(config.api_key, config.headers)
63
+ self.use_post = use_post
64
+
65
+ def _make_request(
66
+ self,
67
+ endpoint: str,
68
+ params: Optional[Dict[str, Any]] = None,
69
+ path_params: Optional[Dict[str, str]] = None,
70
+ timeout: int = 30,
71
+ retries: int = 3,
72
+ ) -> Dict[str, Any]:
73
+ """Make HTTP request to Tempo API with retry logic.
74
+
75
+ Args:
76
+ endpoint: API endpoint path (e.g., "/api/echo")
77
+ params: Query parameters (GET) or body parameters (POST)
78
+ path_params: Parameters to substitute in the endpoint path
79
+ timeout: Request timeout in seconds
80
+ retries: Number of retry attempts
81
+
82
+ Returns:
83
+ JSON response from the API
84
+
85
+ Raises:
86
+ Exception: If the request fails after all retries
87
+ """
88
+ # Format endpoint with path parameters
89
+ if path_params:
90
+ for key, value in path_params.items():
91
+ endpoint = endpoint.replace(f"{{{key}}}", quote(str(value), safe=""))
92
+
93
+ url = f"{self.base_url}{endpoint}"
94
+
95
+ @backoff.on_exception(
96
+ backoff.expo,
97
+ requests.exceptions.RequestException,
98
+ max_tries=retries,
99
+ giveup=lambda e: isinstance(e, requests.exceptions.HTTPError)
100
+ and getattr(e, "response", None) is not None
101
+ and e.response.status_code < 500,
102
+ )
103
+ def make_request():
104
+ if self.use_post:
105
+ # POST request with JSON body
106
+ response = requests.post(
107
+ url,
108
+ headers=self.headers,
109
+ json=params or {},
110
+ timeout=timeout,
111
+ )
112
+ else:
113
+ # GET request with query parameters
114
+ response = requests.get(
115
+ url,
116
+ headers=self.headers,
117
+ params=params,
118
+ timeout=timeout,
119
+ )
120
+ response.raise_for_status()
121
+ return response.json()
122
+
123
+ try:
124
+ return make_request()
125
+ except requests.exceptions.HTTPError as e:
126
+ # Extract detailed error message from response
127
+ response = e.response
128
+ if response is not None:
129
+ logger.error(
130
+ f"HTTP error {response.status_code} for {url}: {response.text}"
131
+ )
132
+ raise TempoAPIError(
133
+ status_code=response.status_code,
134
+ response_text=response.text,
135
+ url=url,
136
+ )
137
+ else:
138
+ logger.error(f"Request failed for {url}: {e}")
139
+ raise
140
+ except requests.exceptions.RequestException as e:
141
+ logger.error(f"Request failed for {url}: {e}")
142
+ raise
143
+
144
+ def query_echo_endpoint(self) -> bool:
145
+ """Query the echo endpoint to check Tempo status.
146
+
147
+ API Endpoint: GET /api/echo
148
+ HTTP Method: GET (or POST if use_post=True)
149
+
150
+ Returns:
151
+ bool: True if endpoint returns 200 status code, False otherwise
152
+ """
153
+ url = f"{self.base_url}/api/echo"
154
+
155
+ try:
156
+ if self.use_post:
157
+ response = requests.post(
158
+ url,
159
+ headers=self.headers,
160
+ timeout=30,
161
+ )
162
+ else:
163
+ response = requests.get(
164
+ url,
165
+ headers=self.headers,
166
+ timeout=30,
167
+ )
168
+
169
+ # Just check status code, don't try to parse JSON
170
+ return response.status_code == 200
171
+
172
+ except requests.exceptions.RequestException as e:
173
+ logger.error(f"Request failed for {url}: {e}")
174
+ return False
175
+
176
+ def query_trace_by_id_v2(
177
+ self,
178
+ trace_id: str,
179
+ start: Optional[int] = None,
180
+ end: Optional[int] = None,
181
+ ) -> Dict[str, Any]:
182
+ """Query a trace by its ID.
183
+
184
+ API Endpoint: GET /api/v2/traces/{trace_id}
185
+ HTTP Method: GET (or POST if use_post=True)
186
+
187
+ Args:
188
+ trace_id: The trace ID to retrieve
189
+ start: Optional start time in Unix epoch seconds
190
+ end: Optional end time in Unix epoch seconds
191
+
192
+ Returns:
193
+ dict: OpenTelemetry format trace data
194
+ """
195
+ params = {}
196
+ if start is not None:
197
+ params["start"] = str(start)
198
+ if end is not None:
199
+ params["end"] = str(end)
200
+
201
+ return self._make_request(
202
+ "/api/v2/traces/{trace_id}",
203
+ params=params,
204
+ path_params={"trace_id": trace_id},
205
+ )
206
+
207
+ def _search_traces_common(
208
+ self,
209
+ search_params: Dict[str, Any],
210
+ limit: Optional[int] = None,
211
+ start: Optional[int] = None,
212
+ end: Optional[int] = None,
213
+ spss: Optional[int] = None,
214
+ ) -> Dict[str, Any]:
215
+ """Common search implementation for both tag and TraceQL searches.
216
+
217
+ Args:
218
+ search_params: The search-specific parameters (tags or q)
219
+ limit: Optional max number of traces to return
220
+ start: Optional start time in Unix epoch seconds
221
+ end: Optional end time in Unix epoch seconds
222
+ spss: Optional spans per span set
223
+
224
+ Returns:
225
+ dict: Search results with trace metadata
226
+ """
227
+ params = search_params.copy()
228
+
229
+ if limit is not None:
230
+ params["limit"] = str(limit)
231
+ if start is not None:
232
+ params["start"] = str(start)
233
+ if end is not None:
234
+ params["end"] = str(end)
235
+ if spss is not None:
236
+ params["spss"] = str(spss)
237
+
238
+ return self._make_request("/api/search", params=params)
239
+
240
+ def search_traces_by_tags(
241
+ self,
242
+ tags: str,
243
+ min_duration: Optional[str] = None,
244
+ max_duration: Optional[str] = None,
245
+ limit: Optional[int] = None,
246
+ start: Optional[int] = None,
247
+ end: Optional[int] = None,
248
+ spss: Optional[int] = None,
249
+ ) -> Dict[str, Any]:
250
+ """Search for traces using tag-based search.
251
+
252
+ API Endpoint: GET /api/search
253
+ HTTP Method: GET (or POST if use_post=True)
254
+
255
+ Args:
256
+ tags: logfmt-encoded span/process attributes (required)
257
+ min_duration: Optional minimum trace duration (e.g., "5s")
258
+ max_duration: Optional maximum trace duration
259
+ limit: Optional max number of traces to return
260
+ start: Optional start time in Unix epoch seconds
261
+ end: Optional end time in Unix epoch seconds
262
+ spss: Optional spans per span set
263
+
264
+ Returns:
265
+ dict: Search results with trace metadata
266
+ """
267
+ search_params = {"tags": tags}
268
+
269
+ # minDuration and maxDuration are only supported with tag-based search
270
+ if min_duration is not None:
271
+ search_params["minDuration"] = min_duration
272
+ if max_duration is not None:
273
+ search_params["maxDuration"] = max_duration
274
+
275
+ return self._search_traces_common(
276
+ search_params=search_params,
277
+ limit=limit,
278
+ start=start,
279
+ end=end,
280
+ spss=spss,
281
+ )
282
+
283
+ def search_traces_by_query(
284
+ self,
285
+ q: str,
286
+ limit: Optional[int] = None,
287
+ start: Optional[int] = None,
288
+ end: Optional[int] = None,
289
+ spss: Optional[int] = None,
290
+ ) -> Dict[str, Any]:
291
+ """Search for traces using TraceQL query.
292
+
293
+ API Endpoint: GET /api/search
294
+ HTTP Method: GET (or POST if use_post=True)
295
+
296
+ Note: minDuration and maxDuration are not supported with TraceQL queries.
297
+ Use the TraceQL query syntax to filter by duration instead.
298
+
299
+ Args:
300
+ q: TraceQL query (required)
301
+ limit: Optional max number of traces to return
302
+ start: Optional start time in Unix epoch seconds
303
+ end: Optional end time in Unix epoch seconds
304
+ spss: Optional spans per span set
305
+
306
+ Returns:
307
+ dict: Search results with trace metadata
308
+ """
309
+ return self._search_traces_common(
310
+ search_params={"q": q},
311
+ limit=limit,
312
+ start=start,
313
+ end=end,
314
+ spss=spss,
315
+ )
316
+
317
+ def search_tag_names_v2(
318
+ self,
319
+ scope: Optional[str] = None,
320
+ q: Optional[str] = None,
321
+ start: Optional[int] = None,
322
+ end: Optional[int] = None,
323
+ limit: Optional[int] = None,
324
+ max_stale_values: Optional[int] = None,
325
+ ) -> Dict[str, Any]:
326
+ """Search for available tag names.
327
+
328
+ API Endpoint: GET /api/v2/search/tags
329
+ HTTP Method: GET (or POST if use_post=True)
330
+
331
+ Args:
332
+ scope: Optional scope filter ("resource", "span", or "intrinsic")
333
+ q: Optional TraceQL query to filter tags
334
+ start: Optional start time in Unix epoch seconds
335
+ end: Optional end time in Unix epoch seconds
336
+ limit: Optional max number of tag names
337
+ max_stale_values: Optional max stale values parameter
338
+
339
+ Returns:
340
+ dict: Available tag names organized by scope
341
+ """
342
+ params = {}
343
+ if scope is not None:
344
+ params["scope"] = scope
345
+ if q is not None:
346
+ params["q"] = q
347
+ if start is not None:
348
+ params["start"] = str(start)
349
+ if end is not None:
350
+ params["end"] = str(end)
351
+ if limit is not None:
352
+ params["limit"] = str(limit)
353
+ if max_stale_values is not None:
354
+ params["maxStaleValues"] = str(max_stale_values)
355
+
356
+ return self._make_request("/api/v2/search/tags", params=params)
357
+
358
+ def search_tag_values_v2(
359
+ self,
360
+ tag: str,
361
+ q: Optional[str] = None,
362
+ start: Optional[int] = None,
363
+ end: Optional[int] = None,
364
+ limit: Optional[int] = None,
365
+ max_stale_values: Optional[int] = None,
366
+ ) -> Dict[str, Any]:
367
+ """Search for values of a specific tag with optional TraceQL filtering.
368
+
369
+ API Endpoint: GET /api/v2/search/tag/{tag}/values
370
+ HTTP Method: GET (or POST if use_post=True)
371
+
372
+ Args:
373
+ tag: The tag name to get values for (required)
374
+ q: Optional TraceQL query to filter tag values (e.g., '{resource.cluster="us-east-1"}')
375
+ start: Optional start time in Unix epoch seconds
376
+ end: Optional end time in Unix epoch seconds
377
+ limit: Optional max number of values
378
+ max_stale_values: Optional max stale values parameter
379
+
380
+ Returns:
381
+ dict: List of discovered values for the tag
382
+ """
383
+ params = {}
384
+ if q is not None:
385
+ params["q"] = q
386
+ if start is not None:
387
+ params["start"] = str(start)
388
+ if end is not None:
389
+ params["end"] = str(end)
390
+ if limit is not None:
391
+ params["limit"] = str(limit)
392
+ if max_stale_values is not None:
393
+ params["maxStaleValues"] = str(max_stale_values)
394
+
395
+ return self._make_request(
396
+ "/api/v2/search/tag/{tag}/values",
397
+ params=params,
398
+ path_params={"tag": tag},
399
+ )
400
+
401
+ def query_metrics_instant(
402
+ self,
403
+ q: str,
404
+ start: Optional[Union[int, str]] = None,
405
+ end: Optional[Union[int, str]] = None,
406
+ since: Optional[str] = None,
407
+ ) -> Dict[str, Any]:
408
+ """Query TraceQL metrics for an instant value.
409
+
410
+ Computes a single value across the entire time range.
411
+
412
+ API Endpoint: GET /api/metrics/query
413
+ HTTP Method: GET (or POST if use_post=True)
414
+
415
+ Args:
416
+ q: TraceQL metrics query (required)
417
+ start: Optional start time (Unix seconds/nanoseconds/RFC3339)
418
+ end: Optional end time (Unix seconds/nanoseconds/RFC3339)
419
+ since: Optional duration string (e.g., "1h")
420
+
421
+ Returns:
422
+ dict: Single computed metric value
423
+ """
424
+ params = {"q": q}
425
+ if start is not None:
426
+ params["start"] = str(start)
427
+ if end is not None:
428
+ params["end"] = str(end)
429
+ if since is not None:
430
+ params["since"] = since
431
+
432
+ return self._make_request("/api/metrics/query", params=params)
433
+
434
+ def query_metrics_range(
435
+ self,
436
+ q: str,
437
+ step: Optional[str] = None,
438
+ start: Optional[Union[int, str]] = None,
439
+ end: Optional[Union[int, str]] = None,
440
+ since: Optional[str] = None,
441
+ exemplars: Optional[int] = None,
442
+ ) -> Dict[str, Any]:
443
+ """Query TraceQL metrics for a time series range.
444
+
445
+ Returns metrics computed at regular intervals over the time range.
446
+
447
+ API Endpoint: GET /api/metrics/query_range
448
+ HTTP Method: GET (or POST if use_post=True)
449
+
450
+ Args:
451
+ q: TraceQL metrics query (required)
452
+ step: Optional time series granularity (e.g., "1m", "5m")
453
+ start: Optional start time (Unix seconds/nanoseconds/RFC3339)
454
+ end: Optional end time (Unix seconds/nanoseconds/RFC3339)
455
+ since: Optional duration string (e.g., "3h")
456
+ exemplars: Optional maximum number of exemplars to return
457
+
458
+ Returns:
459
+ dict: Time series of metric values
460
+ """
461
+ params = {"q": q}
462
+ if step is not None:
463
+ params["step"] = step
464
+ if start is not None:
465
+ params["start"] = str(start)
466
+ if end is not None:
467
+ params["end"] = str(end)
468
+ if since is not None:
469
+ params["since"] = since
470
+ if exemplars is not None:
471
+ params["exemplars"] = str(exemplars)
472
+
473
+ return self._make_request("/api/metrics/query_range", params=params)
@@ -1,12 +1,147 @@
1
- Use Tempo when investigating latency or performance issues. Tempo provides traces information for application running on the cluster.
1
+ Grafana Tempo provides distributed tracing data through its REST API. Each tool maps directly to a specific Tempo API endpoint.
2
+
2
3
  Assume every application provides tempo traces.
3
- 1. Start by identifying an initial filter to use. This can be a pod name, a deployment name or a service name
4
- 2. Call fetch_tempo_traces_comparative_sample first when investigating performance issues via traces. This tool provides comprehensive analysis for identifying patterns. For other issues not related to performance, you can start with fetch_tempo_traces.
5
- 3. Use `fetch_tempo_traces` setting the appropriate query params
6
- - Use the min_duration filter to ensure you get traces that trigger the alert when you are investigating a performance issue
7
- - If possible, use start and end date to narrow down your search.
8
- - Use fetch_finding_by_id if you are provided with a finding/alert id. It will contain details about when the alert was triggered
9
- - Use at least one of the following argument to ensure you get relevant traces: `service_name`, `pod_name` or `deployment_name`.
10
- 4. When you have a specific trace ID to investigate, use `fetch_tempo_trace_by_id` to get detailed information about that trace.
11
- 5. Look at the duration of each span in any single trace and deduce any issues.
12
- 6. ALWAYS fetch the logs for a pod once you identify a span that is taking a long time. There may be an explanation for the slowness in the logs.
4
+
5
+ ## API Endpoints and Tool Mapping
6
+
7
+ 1. **Trace Search** (GET /api/search)
8
+ - `search_traces_by_query`: Use with 'q' parameter for TraceQL queries
9
+ - `search_traces_by_tags`: Use with 'tags' parameter for logfmt queries
10
+
11
+ 2. **Trace Details** (GET /api/v2/traces/{trace_id})
12
+ - `query_trace_by_id`: Retrieve full trace data
13
+
14
+ 3. **Tag Discovery**
15
+ - `search_tag_names` (GET /api/v2/search/tags): List available tags
16
+ - `search_tag_values` (GET /api/v2/search/tag/{tag}/values): Get values for a tag
17
+
18
+ 4. **TraceQL Metrics**
19
+ - `query_metrics_instant` (GET /api/metrics/query): Single value computation
20
+ - `query_metrics_range` (GET /api/metrics/query_range): Time series data
21
+
22
+ ## Usage Workflow
23
+
24
+ ### 1. Discovering Available Data
25
+ Start by understanding what tags and values exist:
26
+ - Use `search_tag_names` to discover available tags
27
+ - Use `search_tag_values` to see all values for a specific tag (e.g., service names)
28
+
29
+ ### 2. Searching for Traces
30
+ **TraceQL Search (recommended):**
31
+ Use `search_traces_by_query` with TraceQL syntax for powerful filtering:
32
+ - Find errors: `{span.http.status_code>=400}`
33
+ - Service traces: `{resource.service.name="api"}`
34
+ - Slow traces: `{duration>100ms}`
35
+ - Complex queries: `{resource.service.name="api" && span.http.status_code=500 && duration>1s}`
36
+
37
+ **Tag-based Search (legacy):**
38
+ Use `search_traces_by_tags` with logfmt format when you need min/max duration filters:
39
+ - Example: `resource.service.name="api" http.status_code="500"`
40
+ - Supports `min_duration` and `max_duration` parameters
41
+
42
+ ### 3. Analyzing Specific Traces
43
+ When you have trace IDs from search results:
44
+ - Use `query_trace_by_id` to get full trace details
45
+ - Examine spans for errors, slow operations, and bottlenecks
46
+
47
+ ### 4. Computing Metrics from Traces
48
+ **TraceQL metrics** compute aggregated metrics from your trace data, helping you answer critical questions like:
49
+ - How many database calls across all systems are downstream of your application?
50
+ - What services beneath a given endpoint are failing?
51
+ - What services beneath an endpoint are slow?
52
+
53
+ TraceQL metrics parse your traces in aggregate to provide RED (Rate, Error, Duration) metrics from trace data.
54
+
55
+ **Supported Functions:**
56
+ - `rate` - Calculate rate of spans/traces
57
+ - `count_over_time` - Count spans/traces over time
58
+ - `sum_over_time` - Sum span attributes
59
+ - `avg_over_time` - Average of span attributes
60
+ - `max_over_time` - Maximum value over time
61
+ - `min_over_time` - Minimum value over time
62
+ - `quantile_over_time` - Calculate quantiles
63
+ - `histogram_over_time` - Generate histogram data
64
+ - `compare` - Compare metrics between time periods
65
+
66
+ **Modifiers:**
67
+ - `topk` - Return top N results
68
+ - `bottomk` - Return bottom N results
69
+
70
+ **TraceQL Metrics Query Examples:**
71
+
72
+ 1. **rate** - Calculate error rate by service and HTTP route:
73
+ ```
74
+ { resource.service.name = "foo" && status = error } | rate() by (span.http.route)
75
+ ```
76
+
77
+ 2. **count_over_time** - Count spans by HTTP status code:
78
+ ```
79
+ { name = "GET /:endpoint" } | count_over_time() by (span.http.status_code)
80
+ ```
81
+
82
+ 3. **sum_over_time** - Sum HTTP response sizes by service:
83
+ ```
84
+ { name = "GET /:endpoint" } | sum_over_time(span.http.response.size) by (resource.service.name)
85
+ ```
86
+
87
+ 4. **avg_over_time** - Average duration by HTTP status code:
88
+ ```
89
+ { name = "GET /:endpoint" } | avg_over_time(duration) by (span.http.status_code)
90
+ ```
91
+
92
+ 5. **max_over_time** - Maximum response size by HTTP target:
93
+ ```
94
+ { name = "GET /:endpoint" } | max_over_time(span.http.response.size) by (span.http.target)
95
+ ```
96
+
97
+ 6. **min_over_time** - Minimum duration by HTTP target:
98
+ ```
99
+ { name = "GET /:endpoint" } | min_over_time(duration) by (span.http.target)
100
+ ```
101
+
102
+ 7. **quantile_over_time** - Calculate multiple percentiles (99th, 90th, 50th) with exemplars:
103
+ ```
104
+ { span:name = "GET /:endpoint" } | quantile_over_time(duration, .99, .9, .5) by (span.http.target) with (exemplars=true)
105
+ ```
106
+
107
+ 8. **histogram_over_time** - Build duration histogram grouped by custom attribute:
108
+ ```
109
+ { name = "GET /:endpoint" } | histogram_over_time(duration) by (span.foo)
110
+ ```
111
+
112
+ 9. **compare** - Compare error spans against baseline (10 attributes):
113
+ ```
114
+ { resource.service.name="a" && span.http.path="/myapi" } | compare({status=error}, 10)
115
+ ```
116
+
117
+ 10. **Using topk modifier** - Find top 10 endpoints by request rate:
118
+ ```
119
+ { resource.service.name = "foo" } | rate() by (span.http.url) | topk(10)
120
+ ```
121
+
122
+ **Choosing Between Instant and Range Queries:**
123
+
124
+ **Instant Metrics** (`query_metrics_instant`) - Returns a single aggregated value for the entire time range. Use this when:
125
+ - You need a total count or sum across the whole period
126
+ - You want a single metric value (e.g., total error count, average latency)
127
+ - You don't need to see how the metric changes over time
128
+ - You're computing a KPI or summary statistic
129
+
130
+ **Time Series Metrics** (`query_metrics_range`) - Returns values at regular intervals controlled by the 'step' parameter. Use this when:
131
+ - You need to graph metrics over time or analyze trends
132
+ - You want to see patterns, spikes, or changes in metrics
133
+ - You're troubleshooting time-based issues
134
+ - You need to correlate metrics with specific time periods
135
+
136
+ ## Special workflow for performance issues
137
+ When investigating performance issues in kubernetes via traces, call fetch_tempo_traces_comparative_sample. This tool provides comprehensive analysis for identifying patterns.
138
+
139
+ ## Important Notes
140
+ - TraceQL is the modern query language - prefer it over tag-based search
141
+ - TraceQL metrics are computed from trace data, not traditional Prometheus metrics
142
+ - TraceQL metrics is an experimental feature that computes RED (Rate, Error, Duration) metrics from trace data
143
+ - Common attributes to use in queries: resource.service.name, span.http.route, span.http.status_code, span.http.target, status, name, duration
144
+ - All timestamps can be Unix epoch seconds or RFC3339 format
145
+ - Use time filters (start/end) to improve query performance
146
+ - To get information about Kubernetes resources try these first: resource.service.name, resource.k8s.pod.name, resource.k8s.namespace.name, resource.k8s.deployment.name, resource.k8s.node.name, resource.k8s.container.name
147
+ - TraceQL and TraceQL metrics language are complex. If you get empty data, try to simplify your query and try again!