holmesgpt 0.13.3__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.
- holmes/__init__.py +1 -1
- holmes/core/tool_calling_llm.py +95 -78
- holmes/core/tracing.py +6 -1
- holmes/plugins/toolsets/datadog/toolset_datadog_general.py +1 -2
- holmes/plugins/toolsets/datadog/toolset_datadog_logs.py +1 -2
- holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py +1 -2
- holmes/plugins/toolsets/datadog/toolset_datadog_traces.py +1 -2
- holmes/plugins/toolsets/git.py +1 -1
- holmes/plugins/toolsets/grafana/common.py +12 -0
- holmes/plugins/toolsets/grafana/grafana_tempo_api.py +473 -0
- holmes/plugins/toolsets/grafana/toolset_grafana_tempo.jinja2 +146 -11
- holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py +773 -259
- holmes/plugins/toolsets/grafana/trace_parser.py +1 -1
- holmes/plugins/toolsets/newrelic.py +3 -3
- {holmesgpt-0.13.3.dist-info → holmesgpt-0.14.0a0.dist-info}/METADATA +2 -2
- {holmesgpt-0.13.3.dist-info → holmesgpt-0.14.0a0.dist-info}/RECORD +19 -19
- holmes/plugins/toolsets/grafana/tempo_api.py +0 -124
- {holmesgpt-0.13.3.dist-info → holmesgpt-0.14.0a0.dist-info}/LICENSE.txt +0 -0
- {holmesgpt-0.13.3.dist-info → holmesgpt-0.14.0a0.dist-info}/WHEEL +0 -0
- {holmesgpt-0.13.3.dist-info → holmesgpt-0.14.0a0.dist-info}/entry_points.txt +0 -0
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
|
-
import
|
|
3
|
-
from typing import Any, Dict, List, cast
|
|
2
|
+
from typing import Any, Dict, Tuple, cast, List
|
|
4
3
|
|
|
5
|
-
import requests # type: ignore
|
|
6
4
|
import yaml # type: ignore
|
|
7
|
-
from pydantic import BaseModel
|
|
8
5
|
|
|
9
6
|
from holmes.common.env_vars import load_bool
|
|
10
7
|
from holmes.core.tools import (
|
|
@@ -15,43 +12,25 @@ from holmes.core.tools import (
|
|
|
15
12
|
)
|
|
16
13
|
from holmes.plugins.toolsets.grafana.base_grafana_toolset import BaseGrafanaToolset
|
|
17
14
|
from holmes.plugins.toolsets.grafana.common import (
|
|
18
|
-
|
|
19
|
-
build_headers,
|
|
20
|
-
get_base_url,
|
|
15
|
+
GrafanaTempoConfig,
|
|
21
16
|
)
|
|
22
|
-
from holmes.plugins.toolsets.grafana.
|
|
23
|
-
query_tempo_trace_by_id,
|
|
24
|
-
query_tempo_traces,
|
|
25
|
-
)
|
|
26
|
-
from holmes.plugins.toolsets.grafana.trace_parser import format_traces_list
|
|
17
|
+
from holmes.plugins.toolsets.grafana.grafana_tempo_api import GrafanaTempoAPI
|
|
27
18
|
from holmes.plugins.toolsets.logging_utils.logging_api import (
|
|
28
19
|
DEFAULT_TIME_SPAN_SECONDS,
|
|
29
20
|
)
|
|
30
21
|
from holmes.plugins.toolsets.utils import (
|
|
31
|
-
get_param_or_raise,
|
|
32
|
-
process_timestamps_to_int,
|
|
33
22
|
toolset_name_for_one_liner,
|
|
23
|
+
process_timestamps_to_int,
|
|
34
24
|
)
|
|
35
25
|
|
|
36
26
|
TEMPO_LABELS_ADD_PREFIX = load_bool("TEMPO_LABELS_ADD_PREFIX", True)
|
|
27
|
+
TEMPO_API_USE_POST = False # Use GET method for direct API mapping
|
|
37
28
|
|
|
38
29
|
ONE_HOUR_IN_SECONDS = 3600
|
|
39
30
|
DEFAULT_TRACES_TIME_SPAN_SECONDS = DEFAULT_TIME_SPAN_SECONDS # 7 days
|
|
40
31
|
DEFAULT_TAGS_TIME_SPAN_SECONDS = 8 * ONE_HOUR_IN_SECONDS # 8 hours
|
|
41
32
|
|
|
42
33
|
|
|
43
|
-
class GrafanaTempoLabelsConfig(BaseModel):
|
|
44
|
-
pod: str = "k8s.pod.name"
|
|
45
|
-
namespace: str = "k8s.namespace.name"
|
|
46
|
-
deployment: str = "k8s.deployment.name"
|
|
47
|
-
node: str = "k8s.node.name"
|
|
48
|
-
service: str = "service.name"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
class GrafanaTempoConfig(GrafanaConfig):
|
|
52
|
-
labels: GrafanaTempoLabelsConfig = GrafanaTempoLabelsConfig()
|
|
53
|
-
|
|
54
|
-
|
|
55
34
|
class BaseGrafanaTempoToolset(BaseGrafanaToolset):
|
|
56
35
|
config_class = GrafanaTempoConfig
|
|
57
36
|
|
|
@@ -67,6 +46,23 @@ class BaseGrafanaTempoToolset(BaseGrafanaToolset):
|
|
|
67
46
|
def grafana_config(self) -> GrafanaTempoConfig:
|
|
68
47
|
return cast(GrafanaTempoConfig, self._grafana_config)
|
|
69
48
|
|
|
49
|
+
def prerequisites_callable(self, config: dict[str, Any]) -> Tuple[bool, str]:
|
|
50
|
+
"""Check Tempo connectivity using the echo endpoint."""
|
|
51
|
+
# First call parent to validate config
|
|
52
|
+
success, msg = super().prerequisites_callable(config)
|
|
53
|
+
if not success:
|
|
54
|
+
return success, msg
|
|
55
|
+
|
|
56
|
+
# Then check Tempo-specific echo endpoint
|
|
57
|
+
try:
|
|
58
|
+
api = GrafanaTempoAPI(self.grafana_config, use_post=TEMPO_API_USE_POST)
|
|
59
|
+
if api.query_echo_endpoint():
|
|
60
|
+
return True, "Successfully connected to Tempo"
|
|
61
|
+
else:
|
|
62
|
+
return False, "Failed to connect to Tempo echo endpoint"
|
|
63
|
+
except Exception as e:
|
|
64
|
+
return False, f"Failed to connect to Tempo: {str(e)}"
|
|
65
|
+
|
|
70
66
|
def build_k8s_filters(
|
|
71
67
|
self, params: Dict[str, Any], use_exact_match: bool
|
|
72
68
|
) -> List[str]:
|
|
@@ -107,9 +103,9 @@ class BaseGrafanaTempoToolset(BaseGrafanaToolset):
|
|
|
107
103
|
escaped_value = value.replace('"', '\\"')
|
|
108
104
|
filters.append(f'{prefix}{label}="{escaped_value}"')
|
|
109
105
|
else:
|
|
110
|
-
#
|
|
111
|
-
|
|
112
|
-
filters.append(f'{prefix}{label}=~".*{
|
|
106
|
+
# For partial match, use simple substring matching
|
|
107
|
+
# Don't escape anything - let Tempo handle the regex
|
|
108
|
+
filters.append(f'{prefix}{label}=~".*{value}.*"')
|
|
113
109
|
|
|
114
110
|
return filters
|
|
115
111
|
|
|
@@ -122,207 +118,198 @@ def validate_params(params: Dict[str, Any], expected_params: List[str]):
|
|
|
122
118
|
return f"At least one of the following argument is expected but none were set: {expected_params}"
|
|
123
119
|
|
|
124
120
|
|
|
125
|
-
class GetTempoTraces(Tool):
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
)
|
|
318
|
-
return StructuredToolResult(
|
|
319
|
-
status=ToolResultStatus.SUCCESS,
|
|
320
|
-
data=trace_data,
|
|
321
|
-
params=params,
|
|
322
|
-
)
|
|
323
|
-
|
|
324
|
-
def get_parameterized_one_liner(self, params: Dict) -> str:
|
|
325
|
-
return f"{toolset_name_for_one_liner(self._toolset.name)}: Fetched Tempo Trace (trace_id={params.get('trace_id')})"
|
|
121
|
+
# class GetTempoTraces(Tool):
|
|
122
|
+
# def __init__(self, toolset: BaseGrafanaTempoToolset):
|
|
123
|
+
# super().__init__(
|
|
124
|
+
# name="fetch_tempo_traces",
|
|
125
|
+
# description="""Lists Tempo traces. At least one of `service_name`, `pod_name` or `deployment_name` argument is required.""",
|
|
126
|
+
# parameters={
|
|
127
|
+
# "min_duration": ToolParameter(
|
|
128
|
+
# description="The minimum duration of traces to fetch, e.g., '5s' for 5 seconds.",
|
|
129
|
+
# type="string",
|
|
130
|
+
# required=True,
|
|
131
|
+
# ),
|
|
132
|
+
# "service_name": ToolParameter(
|
|
133
|
+
# description="Filter traces by service name",
|
|
134
|
+
# type="string",
|
|
135
|
+
# required=False,
|
|
136
|
+
# ),
|
|
137
|
+
# "pod_name": ToolParameter(
|
|
138
|
+
# description="Filter traces by pod name",
|
|
139
|
+
# type="string",
|
|
140
|
+
# required=False,
|
|
141
|
+
# ),
|
|
142
|
+
# "namespace_name": ToolParameter(
|
|
143
|
+
# description="Filter traces by namespace",
|
|
144
|
+
# type="string",
|
|
145
|
+
# required=False,
|
|
146
|
+
# ),
|
|
147
|
+
# "deployment_name": ToolParameter(
|
|
148
|
+
# description="Filter traces by deployment name",
|
|
149
|
+
# type="string",
|
|
150
|
+
# required=False,
|
|
151
|
+
# ),
|
|
152
|
+
# "node_name": ToolParameter(
|
|
153
|
+
# description="Filter traces by node",
|
|
154
|
+
# type="string",
|
|
155
|
+
# required=False,
|
|
156
|
+
# ),
|
|
157
|
+
# "start_datetime": ToolParameter(
|
|
158
|
+
# description=f"The beginning time boundary for the trace search period. String in RFC3339 format. If a negative integer, the number of seconds relative to the end_timestamp. Defaults to -{DEFAULT_TRACES_TIME_SPAN_SECONDS}",
|
|
159
|
+
# type="string",
|
|
160
|
+
# required=False,
|
|
161
|
+
# ),
|
|
162
|
+
# "end_datetime": ToolParameter(
|
|
163
|
+
# description="The ending time boundary for the trace search period. String in RFC3339 format. Defaults to NOW().",
|
|
164
|
+
# type="string",
|
|
165
|
+
# required=False,
|
|
166
|
+
# ),
|
|
167
|
+
# "limit": ToolParameter(
|
|
168
|
+
# description="Maximum number of traces to return. Defaults to 50",
|
|
169
|
+
# type="string",
|
|
170
|
+
# required=False,
|
|
171
|
+
# ),
|
|
172
|
+
# "sort": ToolParameter(
|
|
173
|
+
# description="One of 'descending', 'ascending' or 'none' for no sorting. Defaults to descending",
|
|
174
|
+
# type="string",
|
|
175
|
+
# required=False,
|
|
176
|
+
# ),
|
|
177
|
+
# },
|
|
178
|
+
# )
|
|
179
|
+
# self._toolset = toolset
|
|
180
|
+
#
|
|
181
|
+
# def _invoke(self, params: Dict, user_approved: bool = False) -> StructuredToolResult:
|
|
182
|
+
# # Create API instance
|
|
183
|
+
# api = GrafanaTempoAPI(self._toolset.grafana_config, use_post=TEMPO_API_USE_POST)
|
|
184
|
+
#
|
|
185
|
+
# invalid_params_error = validate_params(
|
|
186
|
+
# params, ["service_name", "pod_name", "deployment_name"]
|
|
187
|
+
# )
|
|
188
|
+
# if invalid_params_error:
|
|
189
|
+
# return StructuredToolResult(
|
|
190
|
+
# status=ToolResultStatus.ERROR,
|
|
191
|
+
# error=invalid_params_error,
|
|
192
|
+
# params=params,
|
|
193
|
+
# )
|
|
194
|
+
#
|
|
195
|
+
# start, end = process_timestamps_to_int(
|
|
196
|
+
# params.get("start_datetime"),
|
|
197
|
+
# params.get("end_datetime"),
|
|
198
|
+
# default_time_span_seconds=DEFAULT_TRACES_TIME_SPAN_SECONDS,
|
|
199
|
+
# )
|
|
200
|
+
#
|
|
201
|
+
# filters = self._toolset.build_k8s_filters(params, use_exact_match=True)
|
|
202
|
+
#
|
|
203
|
+
# filters.append(f'duration>{get_param_or_raise(params, "min_duration")}')
|
|
204
|
+
#
|
|
205
|
+
# query = " && ".join(filters)
|
|
206
|
+
# query = f"{{{query}}}"
|
|
207
|
+
#
|
|
208
|
+
# traces = api.search_traces_by_query(
|
|
209
|
+
# q=query,
|
|
210
|
+
# start=start,
|
|
211
|
+
# end=end,
|
|
212
|
+
# limit=params.get("limit", 50),
|
|
213
|
+
# )
|
|
214
|
+
# return StructuredToolResult(
|
|
215
|
+
# status=ToolResultStatus.SUCCESS,
|
|
216
|
+
# data=format_traces_list(traces),
|
|
217
|
+
# params=params,
|
|
218
|
+
# invocation=query,
|
|
219
|
+
# )
|
|
220
|
+
#
|
|
221
|
+
# def get_parameterized_one_liner(self, params: Dict) -> str:
|
|
222
|
+
# return f"{toolset_name_for_one_liner(self._toolset.name)}: Fetched Tempo Traces (min_duration={params.get('min_duration')})"
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
# class GetTempoTags(Tool):
|
|
226
|
+
# def __init__(self, toolset: BaseGrafanaTempoToolset):
|
|
227
|
+
# super().__init__(
|
|
228
|
+
# name="fetch_tempo_tags",
|
|
229
|
+
# description="List the tags available in Tempo",
|
|
230
|
+
# parameters={
|
|
231
|
+
# "start_datetime": ToolParameter(
|
|
232
|
+
# description=f"The beginning time boundary for the search period. String in RFC3339 format. If a negative integer, the number of seconds relative to the end_timestamp. Defaults to -{DEFAULT_TAGS_TIME_SPAN_SECONDS}",
|
|
233
|
+
# type="string",
|
|
234
|
+
# required=False,
|
|
235
|
+
# ),
|
|
236
|
+
# "end_datetime": ToolParameter(
|
|
237
|
+
# description="The ending time boundary for the search period. String in RFC3339 format. Defaults to NOW().",
|
|
238
|
+
# type="string",
|
|
239
|
+
# required=False,
|
|
240
|
+
# ),
|
|
241
|
+
# },
|
|
242
|
+
# )
|
|
243
|
+
# self._toolset = toolset
|
|
244
|
+
#
|
|
245
|
+
# def _invoke(self, params: Dict, user_approved: bool = False) -> StructuredToolResult:
|
|
246
|
+
# # Create API instance
|
|
247
|
+
# api = GrafanaTempoAPI(self._toolset.grafana_config, use_post=TEMPO_API_USE_POST)
|
|
248
|
+
#
|
|
249
|
+
# start, end = process_timestamps_to_int(
|
|
250
|
+
# start=params.get("start_datetime"),
|
|
251
|
+
# end=params.get("end_datetime"),
|
|
252
|
+
# default_time_span_seconds=DEFAULT_TAGS_TIME_SPAN_SECONDS,
|
|
253
|
+
# )
|
|
254
|
+
#
|
|
255
|
+
# try:
|
|
256
|
+
# data = api.search_tag_names_v2(start=start, end=end)
|
|
257
|
+
# return StructuredToolResult(
|
|
258
|
+
# status=ToolResultStatus.SUCCESS,
|
|
259
|
+
# data=yaml.dump(data.get("scopes")),
|
|
260
|
+
# params=params,
|
|
261
|
+
# )
|
|
262
|
+
# except Exception as e:
|
|
263
|
+
# return StructuredToolResult(
|
|
264
|
+
# status=ToolResultStatus.ERROR,
|
|
265
|
+
# error=f"Failed to retrieve tags: {str(e)}",
|
|
266
|
+
# params=params,
|
|
267
|
+
# )
|
|
268
|
+
#
|
|
269
|
+
# def get_parameterized_one_liner(self, params: Dict) -> str:
|
|
270
|
+
# return f"{toolset_name_for_one_liner(self._toolset.name)}: Fetched Tempo tags"
|
|
271
|
+
#
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
# class GetTempoTraceById(Tool):
|
|
275
|
+
# def __init__(self, toolset: BaseGrafanaTempoToolset):
|
|
276
|
+
# super().__init__(
|
|
277
|
+
# name="fetch_tempo_trace_by_id",
|
|
278
|
+
# description="""Retrieves detailed information about a Tempo trace using its trace ID. Use this to investigate a trace.""",
|
|
279
|
+
# parameters={
|
|
280
|
+
# "trace_id": ToolParameter(
|
|
281
|
+
# description="The unique trace ID to fetch.",
|
|
282
|
+
# type="string",
|
|
283
|
+
# required=True,
|
|
284
|
+
# ),
|
|
285
|
+
# },
|
|
286
|
+
# )
|
|
287
|
+
# self._toolset = toolset
|
|
288
|
+
#
|
|
289
|
+
# def _invoke(self, params: Dict, user_approved: bool = False) -> StructuredToolResult:
|
|
290
|
+
# # Create API instance
|
|
291
|
+
# api = GrafanaTempoAPI(self._toolset.grafana_config, use_post=TEMPO_API_USE_POST)
|
|
292
|
+
#
|
|
293
|
+
# labels_mapping = self._toolset.grafana_config.labels
|
|
294
|
+
# labels = list(labels_mapping.model_dump().values())
|
|
295
|
+
#
|
|
296
|
+
# # Get raw trace data
|
|
297
|
+
# trace_data = api.query_trace_by_id_v2(
|
|
298
|
+
# trace_id=get_param_or_raise(params, "trace_id")
|
|
299
|
+
# )
|
|
300
|
+
#
|
|
301
|
+
# # Process the trace data (new API returns raw data)
|
|
302
|
+
# formatted_trace = process_trace(trace_data, labels)
|
|
303
|
+
#
|
|
304
|
+
# return StructuredToolResult(
|
|
305
|
+
# status=ToolResultStatus.SUCCESS,
|
|
306
|
+
# data=formatted_trace,
|
|
307
|
+
# params=params,
|
|
308
|
+
# )
|
|
309
|
+
#
|
|
310
|
+
# def get_parameterized_one_liner(self, params: Dict) -> str:
|
|
311
|
+
# return f"{toolset_name_for_one_liner(self._toolset.name)}: Fetched Tempo Trace (trace_id={params.get('trace_id')})"
|
|
312
|
+
#
|
|
326
313
|
|
|
327
314
|
|
|
328
315
|
class FetchTracesSimpleComparison(Tool):
|
|
@@ -426,20 +413,31 @@ Examples:
|
|
|
426
413
|
default_time_span_seconds=DEFAULT_TRACES_TIME_SPAN_SECONDS,
|
|
427
414
|
)
|
|
428
415
|
|
|
429
|
-
|
|
416
|
+
# Create API instance
|
|
417
|
+
api = GrafanaTempoAPI(
|
|
418
|
+
self._toolset.grafana_config, use_post=TEMPO_API_USE_POST
|
|
419
|
+
)
|
|
430
420
|
|
|
431
421
|
# Step 1: Get all trace summaries
|
|
432
422
|
stats_query = f"{{{base_query}}}"
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
423
|
+
|
|
424
|
+
# Debug log the query (useful for troubleshooting)
|
|
425
|
+
import logging
|
|
426
|
+
|
|
427
|
+
logger = logging.getLogger(__name__)
|
|
428
|
+
logger.info(f"Tempo query: {stats_query}")
|
|
429
|
+
|
|
430
|
+
logger.info(f"start: {start}, end: {end}")
|
|
431
|
+
|
|
432
|
+
all_traces_response = api.search_traces_by_query(
|
|
433
|
+
q=stats_query,
|
|
438
434
|
start=start,
|
|
439
435
|
end=end,
|
|
440
436
|
limit=1000,
|
|
441
437
|
)
|
|
442
438
|
|
|
439
|
+
logger.info(f"Response: {all_traces_response}")
|
|
440
|
+
|
|
443
441
|
traces = all_traces_response.get("traces", [])
|
|
444
442
|
if not traces:
|
|
445
443
|
return StructuredToolResult(
|
|
@@ -488,39 +486,22 @@ Examples:
|
|
|
488
486
|
return None
|
|
489
487
|
|
|
490
488
|
try:
|
|
491
|
-
|
|
492
|
-
response = requests.get(
|
|
493
|
-
url,
|
|
494
|
-
headers=build_headers(
|
|
495
|
-
api_key=self._toolset.grafana_config.api_key,
|
|
496
|
-
additional_headers=self._toolset.grafana_config.headers,
|
|
497
|
-
),
|
|
498
|
-
timeout=5,
|
|
499
|
-
)
|
|
500
|
-
response.raise_for_status()
|
|
489
|
+
trace_data = api.query_trace_by_id_v2(trace_id=trace_id)
|
|
501
490
|
return {
|
|
502
491
|
"traceID": trace_id,
|
|
503
492
|
"durationMs": trace_summary.get("durationMs", 0),
|
|
504
493
|
"rootServiceName": trace_summary.get(
|
|
505
494
|
"rootServiceName", "unknown"
|
|
506
495
|
),
|
|
507
|
-
"traceData":
|
|
496
|
+
"traceData": trace_data, # Raw trace data
|
|
508
497
|
}
|
|
509
|
-
except
|
|
498
|
+
except Exception as e:
|
|
510
499
|
error_msg = f"Failed to fetch full trace: {str(e)}"
|
|
511
|
-
if hasattr(e, "response") and e.response is not None:
|
|
512
|
-
error_msg += f" (Status: {e.response.status_code})"
|
|
513
500
|
return {
|
|
514
501
|
"traceID": trace_id,
|
|
515
502
|
"durationMs": trace_summary.get("durationMs", 0),
|
|
516
503
|
"error": error_msg,
|
|
517
504
|
}
|
|
518
|
-
except (ValueError, KeyError) as e:
|
|
519
|
-
return {
|
|
520
|
-
"traceID": trace_id,
|
|
521
|
-
"durationMs": trace_summary.get("durationMs", 0),
|
|
522
|
-
"error": f"Failed to parse trace data: {str(e)}",
|
|
523
|
-
}
|
|
524
505
|
|
|
525
506
|
# Fetch the selected traces
|
|
526
507
|
result = {
|
|
@@ -553,6 +534,532 @@ Examples:
|
|
|
553
534
|
return f"{toolset_name_for_one_liner(self._toolset.name)}: Simple Tempo Traces Comparison"
|
|
554
535
|
|
|
555
536
|
|
|
537
|
+
# New tools matching GrafanaTempoAPI methods
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
class SearchTracesByQuery(Tool):
|
|
541
|
+
def __init__(self, toolset: BaseGrafanaTempoToolset):
|
|
542
|
+
super().__init__(
|
|
543
|
+
name="search_traces_by_query",
|
|
544
|
+
description=(
|
|
545
|
+
"Search for traces using TraceQL query language. "
|
|
546
|
+
"Uses the Tempo API endpoint: GET /api/search with 'q' parameter. "
|
|
547
|
+
'TraceQL allows complex filtering like: {resource.service.name="api"} && {span.http.status_code=500}'
|
|
548
|
+
),
|
|
549
|
+
parameters={
|
|
550
|
+
"q": ToolParameter(
|
|
551
|
+
description="TraceQL query (e.g., '{resource.service.name=\"api\" && span.http.status_code=500}')",
|
|
552
|
+
type="string",
|
|
553
|
+
required=True,
|
|
554
|
+
),
|
|
555
|
+
"limit": ToolParameter(
|
|
556
|
+
description="Maximum number of traces to return",
|
|
557
|
+
type="integer",
|
|
558
|
+
required=False,
|
|
559
|
+
),
|
|
560
|
+
"start": ToolParameter(
|
|
561
|
+
description="Start time in Unix epoch seconds",
|
|
562
|
+
type="integer",
|
|
563
|
+
required=False,
|
|
564
|
+
),
|
|
565
|
+
"end": ToolParameter(
|
|
566
|
+
description="End time in Unix epoch seconds",
|
|
567
|
+
type="integer",
|
|
568
|
+
required=False,
|
|
569
|
+
),
|
|
570
|
+
"spss": ToolParameter(
|
|
571
|
+
description="Spans per span set",
|
|
572
|
+
type="integer",
|
|
573
|
+
required=False,
|
|
574
|
+
),
|
|
575
|
+
},
|
|
576
|
+
)
|
|
577
|
+
self._toolset = toolset
|
|
578
|
+
|
|
579
|
+
def _invoke(
|
|
580
|
+
self, params: Dict, user_approved: bool = False
|
|
581
|
+
) -> StructuredToolResult:
|
|
582
|
+
api = GrafanaTempoAPI(self._toolset.grafana_config, use_post=TEMPO_API_USE_POST)
|
|
583
|
+
|
|
584
|
+
try:
|
|
585
|
+
result = api.search_traces_by_query(
|
|
586
|
+
q=params["q"],
|
|
587
|
+
limit=params.get("limit"),
|
|
588
|
+
start=params.get("start"),
|
|
589
|
+
end=params.get("end"),
|
|
590
|
+
spss=params.get("spss"),
|
|
591
|
+
)
|
|
592
|
+
return StructuredToolResult(
|
|
593
|
+
status=ToolResultStatus.SUCCESS,
|
|
594
|
+
data=yaml.dump(result, default_flow_style=False),
|
|
595
|
+
params=params,
|
|
596
|
+
)
|
|
597
|
+
except Exception as e:
|
|
598
|
+
return StructuredToolResult(
|
|
599
|
+
status=ToolResultStatus.ERROR,
|
|
600
|
+
error=str(e),
|
|
601
|
+
params=params,
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
def get_parameterized_one_liner(self, params: Dict) -> str:
|
|
605
|
+
return f"{toolset_name_for_one_liner(self._toolset.name)}: Searched traces with TraceQL"
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
class SearchTracesByTags(Tool):
|
|
609
|
+
def __init__(self, toolset: BaseGrafanaTempoToolset):
|
|
610
|
+
super().__init__(
|
|
611
|
+
name="search_traces_by_tags",
|
|
612
|
+
description=(
|
|
613
|
+
"Search for traces using logfmt-encoded tags. "
|
|
614
|
+
"Uses the Tempo API endpoint: GET /api/search with 'tags' parameter. "
|
|
615
|
+
'Example: resource.service.name="api" http.status_code="500"'
|
|
616
|
+
),
|
|
617
|
+
parameters={
|
|
618
|
+
"tags": ToolParameter(
|
|
619
|
+
description='Logfmt-encoded span/process attributes (e.g., \'resource.service.name="api" http.status_code="500"\')',
|
|
620
|
+
type="string",
|
|
621
|
+
required=True,
|
|
622
|
+
),
|
|
623
|
+
"min_duration": ToolParameter(
|
|
624
|
+
description="Minimum trace duration (e.g., '5s', '100ms')",
|
|
625
|
+
type="string",
|
|
626
|
+
required=False,
|
|
627
|
+
),
|
|
628
|
+
"max_duration": ToolParameter(
|
|
629
|
+
description="Maximum trace duration (e.g., '10s', '1000ms')",
|
|
630
|
+
type="string",
|
|
631
|
+
required=False,
|
|
632
|
+
),
|
|
633
|
+
"limit": ToolParameter(
|
|
634
|
+
description="Maximum number of traces to return",
|
|
635
|
+
type="integer",
|
|
636
|
+
required=False,
|
|
637
|
+
),
|
|
638
|
+
"start": ToolParameter(
|
|
639
|
+
description="Start time in Unix epoch seconds",
|
|
640
|
+
type="integer",
|
|
641
|
+
required=False,
|
|
642
|
+
),
|
|
643
|
+
"end": ToolParameter(
|
|
644
|
+
description="End time in Unix epoch seconds",
|
|
645
|
+
type="integer",
|
|
646
|
+
required=False,
|
|
647
|
+
),
|
|
648
|
+
"spss": ToolParameter(
|
|
649
|
+
description="Spans per span set",
|
|
650
|
+
type="integer",
|
|
651
|
+
required=False,
|
|
652
|
+
),
|
|
653
|
+
},
|
|
654
|
+
)
|
|
655
|
+
self._toolset = toolset
|
|
656
|
+
|
|
657
|
+
def _invoke(
|
|
658
|
+
self, params: Dict, user_approved: bool = False
|
|
659
|
+
) -> StructuredToolResult:
|
|
660
|
+
api = GrafanaTempoAPI(self._toolset.grafana_config, use_post=TEMPO_API_USE_POST)
|
|
661
|
+
|
|
662
|
+
try:
|
|
663
|
+
result = api.search_traces_by_tags(
|
|
664
|
+
tags=params["tags"],
|
|
665
|
+
min_duration=params.get("min_duration"),
|
|
666
|
+
max_duration=params.get("max_duration"),
|
|
667
|
+
limit=params.get("limit"),
|
|
668
|
+
start=params.get("start"),
|
|
669
|
+
end=params.get("end"),
|
|
670
|
+
spss=params.get("spss"),
|
|
671
|
+
)
|
|
672
|
+
return StructuredToolResult(
|
|
673
|
+
status=ToolResultStatus.SUCCESS,
|
|
674
|
+
data=yaml.dump(result, default_flow_style=False),
|
|
675
|
+
params=params,
|
|
676
|
+
)
|
|
677
|
+
except Exception as e:
|
|
678
|
+
return StructuredToolResult(
|
|
679
|
+
status=ToolResultStatus.ERROR,
|
|
680
|
+
error=str(e),
|
|
681
|
+
params=params,
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
def get_parameterized_one_liner(self, params: Dict) -> str:
|
|
685
|
+
return f"{toolset_name_for_one_liner(self._toolset.name)}: Searched traces with tags"
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
class QueryTraceById(Tool):
|
|
689
|
+
def __init__(self, toolset: BaseGrafanaTempoToolset):
|
|
690
|
+
super().__init__(
|
|
691
|
+
name="query_trace_by_id",
|
|
692
|
+
description=(
|
|
693
|
+
"Retrieve detailed trace information by trace ID. "
|
|
694
|
+
"Uses the Tempo API endpoint: GET /api/v2/traces/{trace_id}. "
|
|
695
|
+
"Returns the full trace data in OpenTelemetry format."
|
|
696
|
+
),
|
|
697
|
+
parameters={
|
|
698
|
+
"trace_id": ToolParameter(
|
|
699
|
+
description="The unique trace ID to fetch",
|
|
700
|
+
type="string",
|
|
701
|
+
required=True,
|
|
702
|
+
),
|
|
703
|
+
"start": ToolParameter(
|
|
704
|
+
description="Optional start time in Unix epoch seconds",
|
|
705
|
+
type="integer",
|
|
706
|
+
required=False,
|
|
707
|
+
),
|
|
708
|
+
"end": ToolParameter(
|
|
709
|
+
description="Optional end time in Unix epoch seconds",
|
|
710
|
+
type="integer",
|
|
711
|
+
required=False,
|
|
712
|
+
),
|
|
713
|
+
},
|
|
714
|
+
)
|
|
715
|
+
self._toolset = toolset
|
|
716
|
+
|
|
717
|
+
def _invoke(
|
|
718
|
+
self, params: Dict, user_approved: bool = False
|
|
719
|
+
) -> StructuredToolResult:
|
|
720
|
+
api = GrafanaTempoAPI(self._toolset.grafana_config, use_post=TEMPO_API_USE_POST)
|
|
721
|
+
|
|
722
|
+
try:
|
|
723
|
+
trace_data = api.query_trace_by_id_v2(
|
|
724
|
+
trace_id=params["trace_id"],
|
|
725
|
+
start=params.get("start"),
|
|
726
|
+
end=params.get("end"),
|
|
727
|
+
)
|
|
728
|
+
|
|
729
|
+
# Return raw trace data as YAML for readability
|
|
730
|
+
return StructuredToolResult(
|
|
731
|
+
status=ToolResultStatus.SUCCESS,
|
|
732
|
+
data=yaml.dump(trace_data, default_flow_style=False),
|
|
733
|
+
params=params,
|
|
734
|
+
)
|
|
735
|
+
except Exception as e:
|
|
736
|
+
return StructuredToolResult(
|
|
737
|
+
status=ToolResultStatus.ERROR,
|
|
738
|
+
error=str(e),
|
|
739
|
+
params=params,
|
|
740
|
+
)
|
|
741
|
+
|
|
742
|
+
def get_parameterized_one_liner(self, params: Dict) -> str:
|
|
743
|
+
return f"{toolset_name_for_one_liner(self._toolset.name)}: Retrieved trace {params.get('trace_id')}"
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
class SearchTagNames(Tool):
|
|
747
|
+
def __init__(self, toolset: BaseGrafanaTempoToolset):
|
|
748
|
+
super().__init__(
|
|
749
|
+
name="search_tag_names",
|
|
750
|
+
description=(
|
|
751
|
+
"Discover available tag names across traces. "
|
|
752
|
+
"Uses the Tempo API endpoint: GET /api/v2/search/tags. "
|
|
753
|
+
"Returns tags organized by scope (resource, span, intrinsic)."
|
|
754
|
+
),
|
|
755
|
+
parameters={
|
|
756
|
+
"scope": ToolParameter(
|
|
757
|
+
description="Filter by scope: 'resource', 'span', or 'intrinsic'",
|
|
758
|
+
type="string",
|
|
759
|
+
required=False,
|
|
760
|
+
),
|
|
761
|
+
"q": ToolParameter(
|
|
762
|
+
description="TraceQL query to filter tags (e.g., '{resource.cluster=\"us-east-1\"}')",
|
|
763
|
+
type="string",
|
|
764
|
+
required=False,
|
|
765
|
+
),
|
|
766
|
+
"start": ToolParameter(
|
|
767
|
+
description="Start time in Unix epoch seconds",
|
|
768
|
+
type="integer",
|
|
769
|
+
required=False,
|
|
770
|
+
),
|
|
771
|
+
"end": ToolParameter(
|
|
772
|
+
description="End time in Unix epoch seconds",
|
|
773
|
+
type="integer",
|
|
774
|
+
required=False,
|
|
775
|
+
),
|
|
776
|
+
"limit": ToolParameter(
|
|
777
|
+
description="Maximum number of tag names to return",
|
|
778
|
+
type="integer",
|
|
779
|
+
required=False,
|
|
780
|
+
),
|
|
781
|
+
"max_stale_values": ToolParameter(
|
|
782
|
+
description="Maximum stale values parameter",
|
|
783
|
+
type="integer",
|
|
784
|
+
required=False,
|
|
785
|
+
),
|
|
786
|
+
},
|
|
787
|
+
)
|
|
788
|
+
self._toolset = toolset
|
|
789
|
+
|
|
790
|
+
def _invoke(
|
|
791
|
+
self, params: Dict, user_approved: bool = False
|
|
792
|
+
) -> StructuredToolResult:
|
|
793
|
+
api = GrafanaTempoAPI(self._toolset.grafana_config, use_post=TEMPO_API_USE_POST)
|
|
794
|
+
|
|
795
|
+
try:
|
|
796
|
+
result = api.search_tag_names_v2(
|
|
797
|
+
scope=params.get("scope"),
|
|
798
|
+
q=params.get("q"),
|
|
799
|
+
start=params.get("start"),
|
|
800
|
+
end=params.get("end"),
|
|
801
|
+
limit=params.get("limit"),
|
|
802
|
+
max_stale_values=params.get("max_stale_values"),
|
|
803
|
+
)
|
|
804
|
+
return StructuredToolResult(
|
|
805
|
+
status=ToolResultStatus.SUCCESS,
|
|
806
|
+
data=yaml.dump(result, default_flow_style=False),
|
|
807
|
+
params=params,
|
|
808
|
+
)
|
|
809
|
+
except Exception as e:
|
|
810
|
+
return StructuredToolResult(
|
|
811
|
+
status=ToolResultStatus.ERROR,
|
|
812
|
+
error=str(e),
|
|
813
|
+
params=params,
|
|
814
|
+
)
|
|
815
|
+
|
|
816
|
+
def get_parameterized_one_liner(self, params: Dict) -> str:
|
|
817
|
+
return f"{toolset_name_for_one_liner(self._toolset.name)}: Discovered tag names"
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
class SearchTagValues(Tool):
|
|
821
|
+
def __init__(self, toolset: BaseGrafanaTempoToolset):
|
|
822
|
+
super().__init__(
|
|
823
|
+
name="search_tag_values",
|
|
824
|
+
description=(
|
|
825
|
+
"Get all values for a specific tag. "
|
|
826
|
+
"Uses the Tempo API endpoint: GET /api/v2/search/tag/{tag}/values. "
|
|
827
|
+
"Useful for discovering what values exist for a given tag."
|
|
828
|
+
),
|
|
829
|
+
parameters={
|
|
830
|
+
"tag": ToolParameter(
|
|
831
|
+
description="The tag name to get values for (e.g., 'resource.service.name', 'http.status_code')",
|
|
832
|
+
type="string",
|
|
833
|
+
required=True,
|
|
834
|
+
),
|
|
835
|
+
"q": ToolParameter(
|
|
836
|
+
description="TraceQL query to filter tag values (e.g., '{resource.cluster=\"us-east-1\"}')",
|
|
837
|
+
type="string",
|
|
838
|
+
required=False,
|
|
839
|
+
),
|
|
840
|
+
"start": ToolParameter(
|
|
841
|
+
description="Start time in Unix epoch seconds",
|
|
842
|
+
type="integer",
|
|
843
|
+
required=False,
|
|
844
|
+
),
|
|
845
|
+
"end": ToolParameter(
|
|
846
|
+
description="End time in Unix epoch seconds",
|
|
847
|
+
type="integer",
|
|
848
|
+
required=False,
|
|
849
|
+
),
|
|
850
|
+
"limit": ToolParameter(
|
|
851
|
+
description="Maximum number of values to return",
|
|
852
|
+
type="integer",
|
|
853
|
+
required=False,
|
|
854
|
+
),
|
|
855
|
+
"max_stale_values": ToolParameter(
|
|
856
|
+
description="Maximum stale values parameter",
|
|
857
|
+
type="integer",
|
|
858
|
+
required=False,
|
|
859
|
+
),
|
|
860
|
+
},
|
|
861
|
+
)
|
|
862
|
+
self._toolset = toolset
|
|
863
|
+
|
|
864
|
+
def _invoke(
|
|
865
|
+
self, params: Dict, user_approved: bool = False
|
|
866
|
+
) -> StructuredToolResult:
|
|
867
|
+
api = GrafanaTempoAPI(self._toolset.grafana_config, use_post=TEMPO_API_USE_POST)
|
|
868
|
+
|
|
869
|
+
try:
|
|
870
|
+
result = api.search_tag_values_v2(
|
|
871
|
+
tag=params["tag"],
|
|
872
|
+
q=params.get("q"),
|
|
873
|
+
start=params.get("start"),
|
|
874
|
+
end=params.get("end"),
|
|
875
|
+
limit=params.get("limit"),
|
|
876
|
+
max_stale_values=params.get("max_stale_values"),
|
|
877
|
+
)
|
|
878
|
+
return StructuredToolResult(
|
|
879
|
+
status=ToolResultStatus.SUCCESS,
|
|
880
|
+
data=yaml.dump(result, default_flow_style=False),
|
|
881
|
+
params=params,
|
|
882
|
+
)
|
|
883
|
+
except Exception as e:
|
|
884
|
+
return StructuredToolResult(
|
|
885
|
+
status=ToolResultStatus.ERROR,
|
|
886
|
+
error=str(e),
|
|
887
|
+
params=params,
|
|
888
|
+
)
|
|
889
|
+
|
|
890
|
+
def get_parameterized_one_liner(self, params: Dict) -> str:
|
|
891
|
+
return f"{toolset_name_for_one_liner(self._toolset.name)}: Retrieved values for tag '{params.get('tag')}'"
|
|
892
|
+
|
|
893
|
+
|
|
894
|
+
class QueryMetricsInstant(Tool):
|
|
895
|
+
def __init__(self, toolset: BaseGrafanaTempoToolset):
|
|
896
|
+
super().__init__(
|
|
897
|
+
name="query_metrics_instant",
|
|
898
|
+
description=(
|
|
899
|
+
"Compute a single TraceQL metric value across time range. "
|
|
900
|
+
"Uses the Tempo API endpoint: GET /api/metrics/query. "
|
|
901
|
+
"TraceQL metrics compute aggregated metrics from trace data. "
|
|
902
|
+
"Returns a single value for the entire time range. "
|
|
903
|
+
"Basic syntax: {selector} | function(attribute) [by (grouping)]\n\n"
|
|
904
|
+
"TraceQL metrics can help answer questions like:\n"
|
|
905
|
+
"- How many database calls across all systems are downstream of your application?\n"
|
|
906
|
+
"- What services beneath a given endpoint are failing?\n"
|
|
907
|
+
"- What services beneath an endpoint are slow?\n\n"
|
|
908
|
+
"TraceQL metrics help you answer these questions by parsing your traces in aggregate. "
|
|
909
|
+
"The instant version returns a single value for the query and is preferred over "
|
|
910
|
+
"query_metrics_range when you don't need the granularity of a full time-series but want "
|
|
911
|
+
"a total sum or single value computed across the whole time range."
|
|
912
|
+
),
|
|
913
|
+
parameters={
|
|
914
|
+
"q": ToolParameter(
|
|
915
|
+
description=(
|
|
916
|
+
"TraceQL metrics query. Supported functions: rate, count_over_time, "
|
|
917
|
+
"sum_over_time, max_over_time, min_over_time, avg_over_time, "
|
|
918
|
+
"quantile_over_time, histogram_over_time, compare. "
|
|
919
|
+
"Can use topk or bottomk modifiers. "
|
|
920
|
+
"Syntax: {selector} | function(attribute) [by (grouping)]. "
|
|
921
|
+
'Example: {resource.service.name="api"} | avg_over_time(duration)'
|
|
922
|
+
),
|
|
923
|
+
type="string",
|
|
924
|
+
required=True,
|
|
925
|
+
),
|
|
926
|
+
"start": ToolParameter(
|
|
927
|
+
description="Start time (Unix seconds/nanoseconds/RFC3339)",
|
|
928
|
+
type="string",
|
|
929
|
+
required=False,
|
|
930
|
+
),
|
|
931
|
+
"end": ToolParameter(
|
|
932
|
+
description="End time (Unix seconds/nanoseconds/RFC3339)",
|
|
933
|
+
type="string",
|
|
934
|
+
required=False,
|
|
935
|
+
),
|
|
936
|
+
"since": ToolParameter(
|
|
937
|
+
description="Duration string (e.g., '1h', '30m')",
|
|
938
|
+
type="string",
|
|
939
|
+
required=False,
|
|
940
|
+
),
|
|
941
|
+
},
|
|
942
|
+
)
|
|
943
|
+
self._toolset = toolset
|
|
944
|
+
|
|
945
|
+
def _invoke(
|
|
946
|
+
self, params: Dict, user_approved: bool = False
|
|
947
|
+
) -> StructuredToolResult:
|
|
948
|
+
api = GrafanaTempoAPI(self._toolset.grafana_config, use_post=TEMPO_API_USE_POST)
|
|
949
|
+
|
|
950
|
+
try:
|
|
951
|
+
result = api.query_metrics_instant(
|
|
952
|
+
q=params["q"],
|
|
953
|
+
start=params.get("start"),
|
|
954
|
+
end=params.get("end"),
|
|
955
|
+
since=params.get("since"),
|
|
956
|
+
)
|
|
957
|
+
return StructuredToolResult(
|
|
958
|
+
status=ToolResultStatus.SUCCESS,
|
|
959
|
+
data=yaml.dump(result, default_flow_style=False),
|
|
960
|
+
params=params,
|
|
961
|
+
)
|
|
962
|
+
except Exception as e:
|
|
963
|
+
return StructuredToolResult(
|
|
964
|
+
status=ToolResultStatus.ERROR,
|
|
965
|
+
error=str(e),
|
|
966
|
+
params=params,
|
|
967
|
+
)
|
|
968
|
+
|
|
969
|
+
def get_parameterized_one_liner(self, params: Dict) -> str:
|
|
970
|
+
return (
|
|
971
|
+
f"{toolset_name_for_one_liner(self._toolset.name)}: Computed TraceQL metric"
|
|
972
|
+
)
|
|
973
|
+
|
|
974
|
+
|
|
975
|
+
class QueryMetricsRange(Tool):
|
|
976
|
+
def __init__(self, toolset: BaseGrafanaTempoToolset):
|
|
977
|
+
super().__init__(
|
|
978
|
+
name="query_metrics_range",
|
|
979
|
+
description=(
|
|
980
|
+
"Get time series data from TraceQL metrics queries. "
|
|
981
|
+
"Uses the Tempo API endpoint: GET /api/metrics/query_range. "
|
|
982
|
+
"Returns metrics computed at regular intervals (controlled by 'step' parameter). "
|
|
983
|
+
"Use this for graphing metrics over time or analyzing trends. "
|
|
984
|
+
"Basic syntax: {selector} | function(attribute) [by (grouping)]\n\n"
|
|
985
|
+
"TraceQL metrics can help answer questions like:\n"
|
|
986
|
+
"- How many database calls across all systems are downstream of your application?\n"
|
|
987
|
+
"- What services beneath a given endpoint are failing?\n"
|
|
988
|
+
"- What services beneath an endpoint are slow?\n\n"
|
|
989
|
+
"TraceQL metrics help you answer these questions by parsing your traces in aggregate."
|
|
990
|
+
),
|
|
991
|
+
parameters={
|
|
992
|
+
"q": ToolParameter(
|
|
993
|
+
description=(
|
|
994
|
+
"TraceQL metrics query. Supported functions: rate, count_over_time, "
|
|
995
|
+
"sum_over_time, max_over_time, min_over_time, avg_over_time, "
|
|
996
|
+
"quantile_over_time, histogram_over_time, compare. "
|
|
997
|
+
"Can use topk or bottomk modifiers. "
|
|
998
|
+
"Syntax: {selector} | function(attribute) [by (grouping)]. "
|
|
999
|
+
'Example: {resource.service.name="api"} | avg_over_time(duration)'
|
|
1000
|
+
),
|
|
1001
|
+
type="string",
|
|
1002
|
+
required=True,
|
|
1003
|
+
),
|
|
1004
|
+
"step": ToolParameter(
|
|
1005
|
+
description="Time series granularity (e.g., '1m', '5m', '1h')",
|
|
1006
|
+
type="string",
|
|
1007
|
+
required=False,
|
|
1008
|
+
),
|
|
1009
|
+
"start": ToolParameter(
|
|
1010
|
+
description="Start time (Unix seconds/nanoseconds/RFC3339)",
|
|
1011
|
+
type="string",
|
|
1012
|
+
required=False,
|
|
1013
|
+
),
|
|
1014
|
+
"end": ToolParameter(
|
|
1015
|
+
description="End time (Unix seconds/nanoseconds/RFC3339)",
|
|
1016
|
+
type="string",
|
|
1017
|
+
required=False,
|
|
1018
|
+
),
|
|
1019
|
+
"since": ToolParameter(
|
|
1020
|
+
description="Duration string (e.g., '3h', '1d')",
|
|
1021
|
+
type="string",
|
|
1022
|
+
required=False,
|
|
1023
|
+
),
|
|
1024
|
+
"exemplars": ToolParameter(
|
|
1025
|
+
description="Maximum number of exemplars to return",
|
|
1026
|
+
type="integer",
|
|
1027
|
+
required=False,
|
|
1028
|
+
),
|
|
1029
|
+
},
|
|
1030
|
+
)
|
|
1031
|
+
self._toolset = toolset
|
|
1032
|
+
|
|
1033
|
+
def _invoke(
|
|
1034
|
+
self, params: Dict, user_approved: bool = False
|
|
1035
|
+
) -> StructuredToolResult:
|
|
1036
|
+
api = GrafanaTempoAPI(self._toolset.grafana_config, use_post=TEMPO_API_USE_POST)
|
|
1037
|
+
|
|
1038
|
+
try:
|
|
1039
|
+
result = api.query_metrics_range(
|
|
1040
|
+
q=params["q"],
|
|
1041
|
+
step=params.get("step"),
|
|
1042
|
+
start=params.get("start"),
|
|
1043
|
+
end=params.get("end"),
|
|
1044
|
+
since=params.get("since"),
|
|
1045
|
+
exemplars=params.get("exemplars"),
|
|
1046
|
+
)
|
|
1047
|
+
return StructuredToolResult(
|
|
1048
|
+
status=ToolResultStatus.SUCCESS,
|
|
1049
|
+
data=yaml.dump(result, default_flow_style=False),
|
|
1050
|
+
params=params,
|
|
1051
|
+
)
|
|
1052
|
+
except Exception as e:
|
|
1053
|
+
return StructuredToolResult(
|
|
1054
|
+
status=ToolResultStatus.ERROR,
|
|
1055
|
+
error=str(e),
|
|
1056
|
+
params=params,
|
|
1057
|
+
)
|
|
1058
|
+
|
|
1059
|
+
def get_parameterized_one_liner(self, params: Dict) -> str:
|
|
1060
|
+
return f"{toolset_name_for_one_liner(self._toolset.name)}: Retrieved TraceQL metrics time series"
|
|
1061
|
+
|
|
1062
|
+
|
|
556
1063
|
class GrafanaTempoToolset(BaseGrafanaTempoToolset):
|
|
557
1064
|
def __init__(self):
|
|
558
1065
|
super().__init__(
|
|
@@ -562,9 +1069,16 @@ class GrafanaTempoToolset(BaseGrafanaTempoToolset):
|
|
|
562
1069
|
docs_url="https://holmesgpt.dev/data-sources/builtin-toolsets/grafanatempo/",
|
|
563
1070
|
tools=[
|
|
564
1071
|
FetchTracesSimpleComparison(self),
|
|
565
|
-
GetTempoTraces(self),
|
|
566
|
-
GetTempoTraceById(self),
|
|
567
|
-
GetTempoTags(self),
|
|
1072
|
+
# GetTempoTraces(self),
|
|
1073
|
+
# GetTempoTraceById(self),
|
|
1074
|
+
# GetTempoTags(self),
|
|
1075
|
+
SearchTracesByQuery(self),
|
|
1076
|
+
SearchTracesByTags(self),
|
|
1077
|
+
QueryTraceById(self),
|
|
1078
|
+
SearchTagNames(self),
|
|
1079
|
+
SearchTagValues(self),
|
|
1080
|
+
QueryMetricsInstant(self),
|
|
1081
|
+
QueryMetricsRange(self),
|
|
568
1082
|
],
|
|
569
1083
|
)
|
|
570
1084
|
template_file_path = os.path.abspath(
|