holmesgpt 0.11.5__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.

Files changed (183) hide show
  1. holmes/.git_archival.json +7 -0
  2. holmes/__init__.py +76 -0
  3. holmes/__init__.py.bak +76 -0
  4. holmes/clients/robusta_client.py +24 -0
  5. holmes/common/env_vars.py +47 -0
  6. holmes/config.py +526 -0
  7. holmes/core/__init__.py +0 -0
  8. holmes/core/conversations.py +578 -0
  9. holmes/core/investigation.py +152 -0
  10. holmes/core/investigation_structured_output.py +264 -0
  11. holmes/core/issue.py +54 -0
  12. holmes/core/llm.py +250 -0
  13. holmes/core/models.py +157 -0
  14. holmes/core/openai_formatting.py +51 -0
  15. holmes/core/performance_timing.py +72 -0
  16. holmes/core/prompt.py +42 -0
  17. holmes/core/resource_instruction.py +17 -0
  18. holmes/core/runbooks.py +26 -0
  19. holmes/core/safeguards.py +120 -0
  20. holmes/core/supabase_dal.py +540 -0
  21. holmes/core/tool_calling_llm.py +798 -0
  22. holmes/core/tools.py +566 -0
  23. holmes/core/tools_utils/__init__.py +0 -0
  24. holmes/core/tools_utils/tool_executor.py +65 -0
  25. holmes/core/tools_utils/toolset_utils.py +52 -0
  26. holmes/core/toolset_manager.py +418 -0
  27. holmes/interactive.py +229 -0
  28. holmes/main.py +1041 -0
  29. holmes/plugins/__init__.py +0 -0
  30. holmes/plugins/destinations/__init__.py +6 -0
  31. holmes/plugins/destinations/slack/__init__.py +2 -0
  32. holmes/plugins/destinations/slack/plugin.py +163 -0
  33. holmes/plugins/interfaces.py +32 -0
  34. holmes/plugins/prompts/__init__.py +48 -0
  35. holmes/plugins/prompts/_current_date_time.jinja2 +1 -0
  36. holmes/plugins/prompts/_default_log_prompt.jinja2 +11 -0
  37. holmes/plugins/prompts/_fetch_logs.jinja2 +36 -0
  38. holmes/plugins/prompts/_general_instructions.jinja2 +86 -0
  39. holmes/plugins/prompts/_global_instructions.jinja2 +12 -0
  40. holmes/plugins/prompts/_runbook_instructions.jinja2 +13 -0
  41. holmes/plugins/prompts/_toolsets_instructions.jinja2 +56 -0
  42. holmes/plugins/prompts/generic_ask.jinja2 +36 -0
  43. holmes/plugins/prompts/generic_ask_conversation.jinja2 +32 -0
  44. holmes/plugins/prompts/generic_ask_for_issue_conversation.jinja2 +50 -0
  45. holmes/plugins/prompts/generic_investigation.jinja2 +42 -0
  46. holmes/plugins/prompts/generic_post_processing.jinja2 +13 -0
  47. holmes/plugins/prompts/generic_ticket.jinja2 +12 -0
  48. holmes/plugins/prompts/investigation_output_format.jinja2 +32 -0
  49. holmes/plugins/prompts/kubernetes_workload_ask.jinja2 +84 -0
  50. holmes/plugins/prompts/kubernetes_workload_chat.jinja2 +39 -0
  51. holmes/plugins/runbooks/README.md +22 -0
  52. holmes/plugins/runbooks/__init__.py +100 -0
  53. holmes/plugins/runbooks/catalog.json +14 -0
  54. holmes/plugins/runbooks/jira.yaml +12 -0
  55. holmes/plugins/runbooks/kube-prometheus-stack.yaml +10 -0
  56. holmes/plugins/runbooks/networking/dns_troubleshooting_instructions.md +66 -0
  57. holmes/plugins/runbooks/upgrade/upgrade_troubleshooting_instructions.md +44 -0
  58. holmes/plugins/sources/github/__init__.py +77 -0
  59. holmes/plugins/sources/jira/__init__.py +123 -0
  60. holmes/plugins/sources/opsgenie/__init__.py +93 -0
  61. holmes/plugins/sources/pagerduty/__init__.py +147 -0
  62. holmes/plugins/sources/prometheus/__init__.py +0 -0
  63. holmes/plugins/sources/prometheus/models.py +104 -0
  64. holmes/plugins/sources/prometheus/plugin.py +154 -0
  65. holmes/plugins/toolsets/__init__.py +171 -0
  66. holmes/plugins/toolsets/aks-node-health.yaml +65 -0
  67. holmes/plugins/toolsets/aks.yaml +86 -0
  68. holmes/plugins/toolsets/argocd.yaml +70 -0
  69. holmes/plugins/toolsets/atlas_mongodb/instructions.jinja2 +8 -0
  70. holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py +307 -0
  71. holmes/plugins/toolsets/aws.yaml +76 -0
  72. holmes/plugins/toolsets/azure_sql/__init__.py +0 -0
  73. holmes/plugins/toolsets/azure_sql/apis/alert_monitoring_api.py +600 -0
  74. holmes/plugins/toolsets/azure_sql/apis/azure_sql_api.py +309 -0
  75. holmes/plugins/toolsets/azure_sql/apis/connection_failure_api.py +445 -0
  76. holmes/plugins/toolsets/azure_sql/apis/connection_monitoring_api.py +251 -0
  77. holmes/plugins/toolsets/azure_sql/apis/storage_analysis_api.py +317 -0
  78. holmes/plugins/toolsets/azure_sql/azure_base_toolset.py +55 -0
  79. holmes/plugins/toolsets/azure_sql/azure_sql_instructions.jinja2 +137 -0
  80. holmes/plugins/toolsets/azure_sql/azure_sql_toolset.py +183 -0
  81. holmes/plugins/toolsets/azure_sql/install.md +66 -0
  82. holmes/plugins/toolsets/azure_sql/tools/__init__.py +1 -0
  83. holmes/plugins/toolsets/azure_sql/tools/analyze_connection_failures.py +324 -0
  84. holmes/plugins/toolsets/azure_sql/tools/analyze_database_connections.py +243 -0
  85. holmes/plugins/toolsets/azure_sql/tools/analyze_database_health_status.py +205 -0
  86. holmes/plugins/toolsets/azure_sql/tools/analyze_database_performance.py +249 -0
  87. holmes/plugins/toolsets/azure_sql/tools/analyze_database_storage.py +373 -0
  88. holmes/plugins/toolsets/azure_sql/tools/get_active_alerts.py +237 -0
  89. holmes/plugins/toolsets/azure_sql/tools/get_slow_queries.py +172 -0
  90. holmes/plugins/toolsets/azure_sql/tools/get_top_cpu_queries.py +170 -0
  91. holmes/plugins/toolsets/azure_sql/tools/get_top_data_io_queries.py +188 -0
  92. holmes/plugins/toolsets/azure_sql/tools/get_top_log_io_queries.py +180 -0
  93. holmes/plugins/toolsets/azure_sql/utils.py +83 -0
  94. holmes/plugins/toolsets/bash/__init__.py +0 -0
  95. holmes/plugins/toolsets/bash/bash_instructions.jinja2 +14 -0
  96. holmes/plugins/toolsets/bash/bash_toolset.py +208 -0
  97. holmes/plugins/toolsets/bash/common/bash.py +52 -0
  98. holmes/plugins/toolsets/bash/common/config.py +14 -0
  99. holmes/plugins/toolsets/bash/common/stringify.py +25 -0
  100. holmes/plugins/toolsets/bash/common/validators.py +24 -0
  101. holmes/plugins/toolsets/bash/grep/__init__.py +52 -0
  102. holmes/plugins/toolsets/bash/kubectl/__init__.py +100 -0
  103. holmes/plugins/toolsets/bash/kubectl/constants.py +96 -0
  104. holmes/plugins/toolsets/bash/kubectl/kubectl_describe.py +66 -0
  105. holmes/plugins/toolsets/bash/kubectl/kubectl_events.py +88 -0
  106. holmes/plugins/toolsets/bash/kubectl/kubectl_get.py +108 -0
  107. holmes/plugins/toolsets/bash/kubectl/kubectl_logs.py +20 -0
  108. holmes/plugins/toolsets/bash/kubectl/kubectl_run.py +46 -0
  109. holmes/plugins/toolsets/bash/kubectl/kubectl_top.py +81 -0
  110. holmes/plugins/toolsets/bash/parse_command.py +103 -0
  111. holmes/plugins/toolsets/confluence.yaml +19 -0
  112. holmes/plugins/toolsets/consts.py +5 -0
  113. holmes/plugins/toolsets/coralogix/api.py +158 -0
  114. holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py +103 -0
  115. holmes/plugins/toolsets/coralogix/utils.py +181 -0
  116. holmes/plugins/toolsets/datadog.py +153 -0
  117. holmes/plugins/toolsets/docker.yaml +46 -0
  118. holmes/plugins/toolsets/git.py +756 -0
  119. holmes/plugins/toolsets/grafana/__init__.py +0 -0
  120. holmes/plugins/toolsets/grafana/base_grafana_toolset.py +54 -0
  121. holmes/plugins/toolsets/grafana/common.py +68 -0
  122. holmes/plugins/toolsets/grafana/grafana_api.py +31 -0
  123. holmes/plugins/toolsets/grafana/loki_api.py +89 -0
  124. holmes/plugins/toolsets/grafana/tempo_api.py +124 -0
  125. holmes/plugins/toolsets/grafana/toolset_grafana.py +102 -0
  126. holmes/plugins/toolsets/grafana/toolset_grafana_loki.py +102 -0
  127. holmes/plugins/toolsets/grafana/toolset_grafana_tempo.jinja2 +10 -0
  128. holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py +299 -0
  129. holmes/plugins/toolsets/grafana/trace_parser.py +195 -0
  130. holmes/plugins/toolsets/helm.yaml +42 -0
  131. holmes/plugins/toolsets/internet/internet.py +275 -0
  132. holmes/plugins/toolsets/internet/notion.py +137 -0
  133. holmes/plugins/toolsets/kafka.py +638 -0
  134. holmes/plugins/toolsets/kubernetes.yaml +255 -0
  135. holmes/plugins/toolsets/kubernetes_logs.py +426 -0
  136. holmes/plugins/toolsets/kubernetes_logs.yaml +42 -0
  137. holmes/plugins/toolsets/logging_utils/__init__.py +0 -0
  138. holmes/plugins/toolsets/logging_utils/logging_api.py +217 -0
  139. holmes/plugins/toolsets/logging_utils/types.py +0 -0
  140. holmes/plugins/toolsets/mcp/toolset_mcp.py +135 -0
  141. holmes/plugins/toolsets/newrelic.py +222 -0
  142. holmes/plugins/toolsets/opensearch/__init__.py +0 -0
  143. holmes/plugins/toolsets/opensearch/opensearch.py +245 -0
  144. holmes/plugins/toolsets/opensearch/opensearch_logs.py +151 -0
  145. holmes/plugins/toolsets/opensearch/opensearch_traces.py +211 -0
  146. holmes/plugins/toolsets/opensearch/opensearch_traces_instructions.jinja2 +12 -0
  147. holmes/plugins/toolsets/opensearch/opensearch_utils.py +166 -0
  148. holmes/plugins/toolsets/prometheus/prometheus.py +818 -0
  149. holmes/plugins/toolsets/prometheus/prometheus_instructions.jinja2 +38 -0
  150. holmes/plugins/toolsets/rabbitmq/api.py +398 -0
  151. holmes/plugins/toolsets/rabbitmq/rabbitmq_instructions.jinja2 +37 -0
  152. holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py +222 -0
  153. holmes/plugins/toolsets/robusta/__init__.py +0 -0
  154. holmes/plugins/toolsets/robusta/robusta.py +235 -0
  155. holmes/plugins/toolsets/robusta/robusta_instructions.jinja2 +24 -0
  156. holmes/plugins/toolsets/runbook/__init__.py +0 -0
  157. holmes/plugins/toolsets/runbook/runbook_fetcher.py +78 -0
  158. holmes/plugins/toolsets/service_discovery.py +92 -0
  159. holmes/plugins/toolsets/servicenow/install.md +37 -0
  160. holmes/plugins/toolsets/servicenow/instructions.jinja2 +3 -0
  161. holmes/plugins/toolsets/servicenow/servicenow.py +198 -0
  162. holmes/plugins/toolsets/slab.yaml +20 -0
  163. holmes/plugins/toolsets/utils.py +137 -0
  164. holmes/plugins/utils.py +14 -0
  165. holmes/utils/__init__.py +0 -0
  166. holmes/utils/cache.py +84 -0
  167. holmes/utils/cert_utils.py +40 -0
  168. holmes/utils/default_toolset_installation_guide.jinja2 +44 -0
  169. holmes/utils/definitions.py +13 -0
  170. holmes/utils/env.py +53 -0
  171. holmes/utils/file_utils.py +56 -0
  172. holmes/utils/global_instructions.py +20 -0
  173. holmes/utils/holmes_status.py +22 -0
  174. holmes/utils/holmes_sync_toolsets.py +80 -0
  175. holmes/utils/markdown_utils.py +55 -0
  176. holmes/utils/pydantic_utils.py +54 -0
  177. holmes/utils/robusta.py +10 -0
  178. holmes/utils/tags.py +97 -0
  179. holmesgpt-0.11.5.dist-info/LICENSE.txt +21 -0
  180. holmesgpt-0.11.5.dist-info/METADATA +400 -0
  181. holmesgpt-0.11.5.dist-info/RECORD +183 -0
  182. holmesgpt-0.11.5.dist-info/WHEEL +4 -0
  183. holmesgpt-0.11.5.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,299 @@
1
+ import os
2
+ from typing import Any, Dict, List, cast
3
+
4
+ import requests # type: ignore
5
+ import yaml # type: ignore
6
+ from pydantic import BaseModel
7
+
8
+ from holmes.common.env_vars import load_bool
9
+ from holmes.core.tools import (
10
+ StructuredToolResult,
11
+ Tool,
12
+ ToolParameter,
13
+ ToolResultStatus,
14
+ )
15
+ from holmes.plugins.toolsets.grafana.base_grafana_toolset import BaseGrafanaToolset
16
+ from holmes.plugins.toolsets.grafana.common import (
17
+ GrafanaConfig,
18
+ build_headers,
19
+ get_base_url,
20
+ )
21
+ from holmes.plugins.toolsets.grafana.tempo_api import (
22
+ query_tempo_trace_by_id,
23
+ query_tempo_traces,
24
+ )
25
+ from holmes.plugins.toolsets.grafana.trace_parser import format_traces_list
26
+ from holmes.plugins.toolsets.utils import get_param_or_raise, process_timestamps_to_int
27
+
28
+ TEMPO_LABELS_ADD_PREFIX = load_bool("TEMPO_LABELS_ADD_PREFIX", True)
29
+
30
+ ONE_HOUR_IN_SECONDS = 3600
31
+
32
+
33
+ class GrafanaTempoLabelsConfig(BaseModel):
34
+ pod: str = "k8s.pod.name"
35
+ namespace: str = "k8s.namespace.name"
36
+ deployment: str = "k8s.deployment.name"
37
+ node: str = "k8s.node.name"
38
+ service: str = "service.name"
39
+
40
+
41
+ class GrafanaTempoConfig(GrafanaConfig):
42
+ labels: GrafanaTempoLabelsConfig = GrafanaTempoLabelsConfig()
43
+
44
+
45
+ class BaseGrafanaTempoToolset(BaseGrafanaToolset):
46
+ config_class = GrafanaTempoConfig
47
+
48
+ def get_example_config(self):
49
+ example_config = GrafanaTempoConfig(
50
+ api_key="YOUR API KEY",
51
+ url="YOUR GRAFANA URL",
52
+ grafana_datasource_uid="<UID of the tempo datasource to use>",
53
+ )
54
+ return example_config.model_dump()
55
+
56
+ @property
57
+ def grafana_config(self) -> GrafanaTempoConfig:
58
+ return cast(GrafanaTempoConfig, self._grafana_config)
59
+
60
+
61
+ def validate_params(params: Dict[str, Any], expected_params: List[str]):
62
+ for param in expected_params:
63
+ if param in params and params[param] not in (None, "", [], {}):
64
+ return None
65
+
66
+ return f"At least one of the following argument is expected but none were set: {expected_params}"
67
+
68
+
69
+ class GetTempoTraces(Tool):
70
+ def __init__(self, toolset: BaseGrafanaTempoToolset):
71
+ super().__init__(
72
+ name="fetch_tempo_traces",
73
+ description="""Lists Tempo traces. At least one of `service_name`, `pod_name` or `deployment_name` argument is required.""",
74
+ parameters={
75
+ "min_duration": ToolParameter(
76
+ description="The minimum duration of traces to fetch, e.g., '5s' for 5 seconds.",
77
+ type="string",
78
+ required=True,
79
+ ),
80
+ "service_name": ToolParameter(
81
+ description="Filter traces by service name",
82
+ type="string",
83
+ required=False,
84
+ ),
85
+ "pod_name": ToolParameter(
86
+ description="Filter traces by pod name",
87
+ type="string",
88
+ required=False,
89
+ ),
90
+ "namespace_name": ToolParameter(
91
+ description="Filter traces by namespace",
92
+ type="string",
93
+ required=False,
94
+ ),
95
+ "deployment_name": ToolParameter(
96
+ description="Filter traces by deployment name",
97
+ type="string",
98
+ required=False,
99
+ ),
100
+ "node_name": ToolParameter(
101
+ description="Filter traces by node",
102
+ type="string",
103
+ required=False,
104
+ ),
105
+ "start_datetime": ToolParameter(
106
+ description="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 negative one hour (-3600)",
107
+ type="string",
108
+ required=False,
109
+ ),
110
+ "end_datetime": ToolParameter(
111
+ description="The ending time boundary for the trace search period. String in RFC3339 format. Defaults to NOW().",
112
+ type="string",
113
+ required=False,
114
+ ),
115
+ "limit": ToolParameter(
116
+ description="Maximum number of traces to return. Defaults to 50",
117
+ type="string",
118
+ required=False,
119
+ ),
120
+ "sort": ToolParameter(
121
+ description="One of 'descending', 'ascending' or 'none' for no sorting. Defaults to descending",
122
+ type="string",
123
+ required=False,
124
+ ),
125
+ },
126
+ )
127
+ self._toolset = toolset
128
+
129
+ def _invoke(self, params: Dict) -> StructuredToolResult:
130
+ api_key = self._toolset.grafana_config.api_key
131
+ headers = self._toolset.grafana_config.headers
132
+ labels = self._toolset.grafana_config.labels
133
+
134
+ invalid_params_error = validate_params(
135
+ params, ["service_name", "pod_name", "deployment_name"]
136
+ )
137
+ if invalid_params_error:
138
+ return StructuredToolResult(
139
+ status=ToolResultStatus.ERROR,
140
+ error=invalid_params_error,
141
+ params=params,
142
+ )
143
+
144
+ start, end = process_timestamps_to_int(
145
+ params.get("start_datetime"),
146
+ params.get("end_datetime"),
147
+ default_time_span_seconds=3600,
148
+ )
149
+
150
+ prefix = ""
151
+ if TEMPO_LABELS_ADD_PREFIX:
152
+ prefix = "resource."
153
+
154
+ filters = []
155
+ if params.get("service_name"):
156
+ filters.append(f'{prefix}{labels.service}="{params.get("service_name")}"')
157
+ if params.get("pod_name"):
158
+ filters.append(f'{prefix}{labels.pod}="{params.get("pod_name")}"')
159
+ if params.get("namespace_name"):
160
+ filters.append(
161
+ f'{prefix}{labels.namespace}="{params.get("namespace_name")}"'
162
+ )
163
+ if params.get("deployment_name"):
164
+ filters.append(
165
+ f'{prefix}{labels.deployment}="{params.get("deployment_name")}"'
166
+ )
167
+ if params.get("node_name"):
168
+ filters.append(f'{prefix}{labels.node}="{params.get("node_name")}"')
169
+
170
+ filters.append(f'duration>{get_param_or_raise(params, "min_duration")}')
171
+
172
+ query = " && ".join(filters)
173
+ query = f"{{{query}}}"
174
+
175
+ base_url = get_base_url(self._toolset.grafana_config)
176
+ traces = query_tempo_traces(
177
+ base_url=base_url,
178
+ api_key=api_key,
179
+ headers=headers,
180
+ query=query,
181
+ start=start,
182
+ end=end,
183
+ limit=params.get("limit", 50),
184
+ )
185
+ return StructuredToolResult(
186
+ status=ToolResultStatus.SUCCESS,
187
+ data=format_traces_list(traces),
188
+ params=params,
189
+ invocation=query,
190
+ )
191
+
192
+ def get_parameterized_one_liner(self, params: Dict) -> str:
193
+ return f"Fetched Tempo traces with min_duration={params.get('min_duration')} ({str(params)})"
194
+
195
+
196
+ class GetTempoTags(Tool):
197
+ def __init__(self, toolset: BaseGrafanaTempoToolset):
198
+ super().__init__(
199
+ name="fetch_tempo_tags",
200
+ description="List the tags available in Tempo",
201
+ parameters={
202
+ "start_datetime": ToolParameter(
203
+ description="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 negative 8 hours (-3600)",
204
+ type="string",
205
+ required=False,
206
+ ),
207
+ "end_datetime": ToolParameter(
208
+ description="The ending time boundary for the search period. String in RFC3339 format. Defaults to NOW().",
209
+ type="string",
210
+ required=False,
211
+ ),
212
+ },
213
+ )
214
+ self._toolset = toolset
215
+
216
+ def _invoke(self, params: Dict) -> StructuredToolResult:
217
+ api_key = self._toolset.grafana_config.api_key
218
+ headers = self._toolset.grafana_config.headers
219
+ start, end = process_timestamps_to_int(
220
+ start=params.get("start_datetime"),
221
+ end=params.get("end_datetime"),
222
+ default_time_span_seconds=8 * ONE_HOUR_IN_SECONDS,
223
+ )
224
+
225
+ base_url = get_base_url(self._toolset.grafana_config)
226
+ url = f"{base_url}/api/v2/search/tags?start={start}&end={end}"
227
+
228
+ try:
229
+ response = requests.get(
230
+ url,
231
+ headers=build_headers(api_key=api_key, additional_headers=headers),
232
+ timeout=60,
233
+ )
234
+ response.raise_for_status() # Raise an error for non-2xx responses
235
+ data = response.json()
236
+ return StructuredToolResult(
237
+ status=ToolResultStatus.SUCCESS,
238
+ data=yaml.dump(data.get("scopes")),
239
+ params=params,
240
+ )
241
+ except requests.exceptions.RequestException as e:
242
+ raise Exception(
243
+ f"Failed to retrieve trace by ID after retries: {e} \n for URL: {url}"
244
+ )
245
+
246
+ def get_parameterized_one_liner(self, params: Dict) -> str:
247
+ return f"Fetched Tempo tags ({str(params)})"
248
+
249
+
250
+ class GetTempoTraceById(Tool):
251
+ def __init__(self, toolset: BaseGrafanaTempoToolset):
252
+ super().__init__(
253
+ name="fetch_tempo_trace_by_id",
254
+ description="""Retrieves detailed information about a Tempo trace using its trace ID. Use this to investigate a trace.""",
255
+ parameters={
256
+ "trace_id": ToolParameter(
257
+ description="The unique trace ID to fetch.",
258
+ type="string",
259
+ required=True,
260
+ ),
261
+ },
262
+ )
263
+ self._toolset = toolset
264
+
265
+ def _invoke(self, params: Dict) -> StructuredToolResult:
266
+ labels_mapping = self._toolset.grafana_config.labels
267
+ labels = list(labels_mapping.model_dump().values())
268
+
269
+ base_url = get_base_url(self._toolset.grafana_config)
270
+ trace_data = query_tempo_trace_by_id(
271
+ base_url=base_url,
272
+ api_key=self._toolset.grafana_config.api_key,
273
+ headers=self._toolset.grafana_config.headers,
274
+ trace_id=get_param_or_raise(params, "trace_id"),
275
+ key_labels=labels,
276
+ )
277
+ return StructuredToolResult(
278
+ status=ToolResultStatus.SUCCESS,
279
+ data=trace_data,
280
+ params=params,
281
+ )
282
+
283
+ def get_parameterized_one_liner(self, params: Dict) -> str:
284
+ return f"Fetched Tempo trace with trace_id={params.get('trace_id')} ({str(params)})"
285
+
286
+
287
+ class GrafanaTempoToolset(BaseGrafanaTempoToolset):
288
+ def __init__(self):
289
+ super().__init__(
290
+ name="grafana/tempo",
291
+ description="Fetches kubernetes traces from Tempo",
292
+ icon_url="https://grafana.com/static/assets/img/blog/tempo.png",
293
+ docs_url="https://docs.robusta.dev/master/configuration/holmesgpt/toolsets/grafanatempo.html",
294
+ tools=[GetTempoTraces(self), GetTempoTraceById(self), GetTempoTags(self)],
295
+ )
296
+ template_file_path = os.path.abspath(
297
+ os.path.join(os.path.dirname(__file__), "toolset_grafana_tempo.jinja2")
298
+ )
299
+ self._load_llm_instructions(jinja_template=f"file://{template_file_path}")
@@ -0,0 +1,195 @@
1
+ from typing import Dict, List, Optional, Any
2
+ from dataclasses import dataclass, field
3
+ import base64
4
+ from holmes.plugins.toolsets.utils import unix_nano_to_rfc3339
5
+
6
+
7
+ @dataclass
8
+ class Span:
9
+ span_id: str
10
+ parent_span_id: Optional[str]
11
+ name: str
12
+ service_name: str
13
+ start_time: int
14
+ end_time: int
15
+ attributes: Dict[str, Any] = field(default_factory=dict)
16
+ events: List[Dict[str, Any]] = field(default_factory=list)
17
+ children: List["Span"] = field(default_factory=list)
18
+ resource_attributes: Dict[str, Any] = field(default_factory=dict)
19
+
20
+ @property
21
+ def duration_ms(self) -> float:
22
+ """Calculate duration in milliseconds"""
23
+ return (
24
+ self.end_time - self.start_time
25
+ ) / 1_000_000 # Convert nanoseconds to milliseconds
26
+
27
+
28
+ def decode_id(encoded_id: str) -> str:
29
+ """Decode base64 IDs to a hex string for easier reading"""
30
+ return base64.b64decode(encoded_id).hex()
31
+
32
+
33
+ def build_span_hierarchy(trace_data: Dict) -> List[Span]:
34
+ # Step 1: Extract all spans and create span objects
35
+ all_spans = {}
36
+
37
+ for batch in trace_data["batches"]:
38
+ # Extract service name and other resource attributes
39
+ service_name = "unknown"
40
+ resource_attributes = {}
41
+
42
+ for attr in batch["resource"]["attributes"]:
43
+ key = attr["key"]
44
+ value = list(attr["value"].values())[0]
45
+ resource_attributes[key] = value
46
+
47
+ if key == "service.name":
48
+ service_name = value
49
+
50
+ for scope_spans in batch["scopeSpans"]:
51
+ for span_data in scope_spans["spans"]:
52
+ span_id = decode_id(span_data["spanId"])
53
+
54
+ # Get parent span ID if it exists
55
+ parent_span_id = None
56
+ if "parentSpanId" in span_data:
57
+ parent_span_id = decode_id(span_data["parentSpanId"])
58
+
59
+ # Create a Span object
60
+ span = Span(
61
+ span_id=span_id,
62
+ parent_span_id=parent_span_id,
63
+ name=span_data["name"],
64
+ service_name=service_name,
65
+ start_time=int(span_data["startTimeUnixNano"]),
66
+ end_time=int(span_data["endTimeUnixNano"]),
67
+ attributes={
68
+ attr["key"]: list(attr["value"].values())[0]
69
+ for attr in span_data.get("attributes", [])
70
+ },
71
+ events=span_data.get("events", []),
72
+ resource_attributes=resource_attributes,
73
+ )
74
+
75
+ all_spans[span_id] = span
76
+
77
+ # Step 2: Build the hierarchy by connecting parents and children
78
+ root_spans = []
79
+
80
+ for span in all_spans.values():
81
+ if span.parent_span_id is None:
82
+ # This is a root span
83
+ root_spans.append(span)
84
+ else:
85
+ # Add this span as a child to its parent
86
+ if span.parent_span_id in all_spans:
87
+ all_spans[span.parent_span_id].children.append(span)
88
+
89
+ return root_spans
90
+
91
+
92
+ def format_labels(attributes, key_labels):
93
+ """Format the specified labels from attributes into a string"""
94
+ result = []
95
+ for key in key_labels:
96
+ if key in attributes:
97
+ result.append(f"{key}='{attributes[key]}'")
98
+
99
+ return " ".join(result) if result else None
100
+
101
+
102
+ def format_span_tree(span: Span, level: int = 0, key_labels: List[str] = []) -> str:
103
+ """Format the span hierarchy in a tree format with timestamps and labels"""
104
+ span_tree_text = ""
105
+ indent = " " * level
106
+ duration = span.duration_ms
107
+
108
+ span_tree_text += f"{indent}├─ {span.name} ({span.service_name}) - {duration:.2f}ms (span_id={span.span_id})\n"
109
+
110
+ start_time_str = unix_nano_to_rfc3339(span.start_time)
111
+ end_time_str = unix_nano_to_rfc3339(span.end_time)
112
+ span_tree_text += (
113
+ f"{indent}│ Datetime: start={start_time_str} end={end_time_str}\n"
114
+ )
115
+
116
+ if key_labels and span.resource_attributes:
117
+ resource_labels = format_labels(span.resource_attributes, key_labels)
118
+ if resource_labels:
119
+ span_tree_text += f"{indent}│ Resource labels: {resource_labels}\n"
120
+
121
+ if span.attributes:
122
+ span_tree_text += f"{indent}│ Attributes:\n"
123
+ for key, value in span.attributes.items():
124
+ span_tree_text += f"{indent}│ {key}: {value}\n"
125
+
126
+ if key_labels:
127
+ span_labels = format_labels(span.attributes, key_labels)
128
+ if span_labels:
129
+ span_tree_text += f"{indent}│ Span labels: {span_labels}\n"
130
+
131
+ if span.events:
132
+ span_tree_text += f"{indent}│ Events:\n"
133
+ for event in span.events:
134
+ event_name = event["name"]
135
+ event_time = int(event["timeUnixNano"])
136
+ event_time_str = unix_nano_to_rfc3339(event_time)
137
+ relative_time = (event_time - span.start_time) / 1_000_000
138
+ span_tree_text += f"{indent}│ {event_name} (+{relative_time:.2f}ms) at {event_time_str}\n"
139
+
140
+ if "attributes" in event and event["attributes"]:
141
+ for attr in event["attributes"]:
142
+ attr_key = attr["key"]
143
+ values = list(attr["value"].values())
144
+ if len(values) == 1:
145
+ span_tree_text += (
146
+ f"{indent}│ {attr_key}: {str(values[0])}\n"
147
+ )
148
+ elif values:
149
+ span_tree_text += f"{indent}│ {attr_key}: {str(values)}\n"
150
+
151
+ for child in sorted(span.children, key=lambda s: s.start_time):
152
+ span_tree_text += format_span_tree(child, level + 1, key_labels)
153
+
154
+ return span_tree_text
155
+
156
+
157
+ def process_trace(
158
+ trace_data: Dict,
159
+ key_labels: List[str] = [
160
+ "service.name",
161
+ "service.version",
162
+ "k8s.deployment.name",
163
+ "k8s.node.name",
164
+ "k8s.pod.name",
165
+ "k8s.namespace.name",
166
+ ],
167
+ ) -> str:
168
+ root_spans = build_span_hierarchy(trace_data)
169
+
170
+ span_trees = []
171
+ for root_span in sorted(root_spans, key=lambda s: s.start_time):
172
+ span_trees.append(format_span_tree(root_span, key_labels=key_labels))
173
+
174
+ return "\n\n".join(span_trees)
175
+
176
+
177
+ def format_traces_list(trace_data: Dict) -> str:
178
+ traces = trace_data.get("traces", [])
179
+
180
+ if len(traces) > 0:
181
+ traces_str = []
182
+ for trace in traces:
183
+ trace_str = f"Trace (traceID={trace.get('traceID')})"
184
+ trace_str += (
185
+ f" (durationMs={trace.get('durationMs')})\n"
186
+ if trace.get("durationMs") is not None
187
+ else "\n"
188
+ )
189
+ trace_str += f"\tstartTime={unix_nano_to_rfc3339(int(trace.get('startTimeUnixNano')))}"
190
+ trace_str += f" rootServiceName={trace.get('trootServiceName')}"
191
+ trace_str += f" rootTraceName={trace.get('rootTraceName')}"
192
+ traces_str.append(trace_str)
193
+ return "\n".join(traces_str)
194
+ else:
195
+ return "No matching trace could be found"
@@ -0,0 +1,42 @@
1
+ toolsets:
2
+ helm/core:
3
+ description: "Read access to cluster's Helm charts and releases"
4
+ docs_url: "https://docs.robusta.dev/master/configuration/holmesgpt/toolsets/helm.html"
5
+ icon_url: "https://helm.sh/img/helm.svg"
6
+ tags:
7
+ - core
8
+ prerequisites:
9
+ - command: "helm version"
10
+
11
+ tools:
12
+ - name: "helm_list"
13
+ description: "Use to get all the current helm releases"
14
+ command: "helm list"
15
+
16
+ - name: "helm_values"
17
+ description: "Use to gather Helm values or any released helm chart"
18
+ command: "helm get values -a {{ release_name }} -n {{ namespace }} -o json"
19
+
20
+ - name: "helm_status"
21
+ description: "Check the status of a Helm release"
22
+ command: "helm status {{ release_name }} -n {{ namespace }}"
23
+
24
+ - name: "helm_history"
25
+ description: "Get the revision history of a Helm release"
26
+ command: "helm history {{ release_name }} -n {{ namespace }}"
27
+
28
+ - name: "helm_manifest"
29
+ description: "Fetch the generated Kubernetes manifest for a Helm release"
30
+ command: "helm get manifest {{ release_name }} -n {{ namespace }}"
31
+
32
+ - name: "helm_hooks"
33
+ description: "Get the hooks for a Helm release"
34
+ command: "helm get hooks {{ release_name }} -n {{ namespace }}"
35
+
36
+ - name: "helm_chart"
37
+ description: "Show the chart used to create a Helm release"
38
+ command: "helm get chart {{ release_name }} -n {{ namespace }}"
39
+
40
+ - name: "helm_notes"
41
+ description: "Show the notes provided by the Helm chart"
42
+ command: "helm get notes {{ release_name }} -n {{ namespace }}"