mcp-instana 0.1.0__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.
- mcp_instana-0.1.0.dist-info/LICENSE +201 -0
- mcp_instana-0.1.0.dist-info/METADATA +649 -0
- mcp_instana-0.1.0.dist-info/RECORD +19 -0
- mcp_instana-0.1.0.dist-info/WHEEL +4 -0
- mcp_instana-0.1.0.dist-info/entry_points.txt +3 -0
- src/__init__.py +0 -0
- src/client/What is the sum of queue depth for all q +55 -0
- src/client/application_alert_config_mcp_tools.py +680 -0
- src/client/application_metrics_mcp_tools.py +377 -0
- src/client/application_resources_mcp_tools.py +391 -0
- src/client/events_mcp_tools.py +531 -0
- src/client/infrastructure_analyze_mcp_tools.py +634 -0
- src/client/infrastructure_catalog_mcp_tools.py +624 -0
- src/client/infrastructure_resources_mcp_tools.py +653 -0
- src/client/infrastructure_topology_mcp_tools.py +319 -0
- src/client/instana_client_base.py +93 -0
- src/client/log_alert_configuration_mcp_tools.py +316 -0
- src/client/show the top 5 services with the highest +28 -0
- src/mcp_server.py +343 -0
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Monitoring Events MCP Tools Module
|
|
3
|
+
|
|
4
|
+
This module provides agent monitoring events-specific MCP tools for Instana monitoring.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Dict, Any, Optional, List, Union
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
|
|
11
|
+
# Import the correct class name (EventsApi with lowercase 'i')
|
|
12
|
+
from instana_client.api.events_api import EventsApi
|
|
13
|
+
|
|
14
|
+
from .instana_client_base import BaseInstanaClient, register_as_tool
|
|
15
|
+
|
|
16
|
+
class AgentMonitoringEventsMCPTools(BaseInstanaClient):
|
|
17
|
+
"""Tools for agent monitoring events in Instana MCP."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, read_token: str, base_url: str):
|
|
20
|
+
"""Initialize the Agent Monitoring Events MCP tools client."""
|
|
21
|
+
super().__init__(read_token=read_token, base_url=base_url)
|
|
22
|
+
|
|
23
|
+
# Configure the API client with the correct base URL and authentication
|
|
24
|
+
from instana_client.configuration import Configuration
|
|
25
|
+
configuration = Configuration()
|
|
26
|
+
configuration.host = base_url
|
|
27
|
+
configuration.api_key['ApiKeyAuth'] = read_token
|
|
28
|
+
configuration.api_key_prefix['ApiKeyAuth'] = 'apiToken'
|
|
29
|
+
|
|
30
|
+
# Create an API client with this configuration
|
|
31
|
+
from instana_client.api_client import ApiClient
|
|
32
|
+
api_client = ApiClient(configuration=configuration)
|
|
33
|
+
|
|
34
|
+
# Initialize the Instana SDK's EventsApi with our configured client
|
|
35
|
+
self.events_api = EventsApi(api_client=api_client)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@register_as_tool
|
|
39
|
+
async def get_event(self, event_id: str, ctx=None) -> Dict[str, Any]:
|
|
40
|
+
"""
|
|
41
|
+
Get a specific event by ID.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
event_id: The ID of the event to retrieve
|
|
45
|
+
ctx: The MCP context (optional)
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Dictionary containing the event data or error information
|
|
49
|
+
"""
|
|
50
|
+
try:
|
|
51
|
+
# Call the get_event method from the SDK
|
|
52
|
+
result = self.events_api.get_event(event_id=event_id)
|
|
53
|
+
|
|
54
|
+
return result
|
|
55
|
+
except Exception as e:
|
|
56
|
+
return {"error": f"Failed to get event: {str(e)}"}
|
|
57
|
+
|
|
58
|
+
@register_as_tool
|
|
59
|
+
async def get_kubernetes_info_events(self,
|
|
60
|
+
from_time: Optional[int] = None,
|
|
61
|
+
to_time: Optional[int] = None,
|
|
62
|
+
time_range: Optional[str] = None,
|
|
63
|
+
max_events: Optional[int] = 50, # Added parameter to limit events
|
|
64
|
+
ctx=None) -> Dict[str, Any]:
|
|
65
|
+
"""
|
|
66
|
+
Get Kubernetes info events based on the provided parameters and return a detailed analysis.
|
|
67
|
+
|
|
68
|
+
This tool retrieves Kubernetes events from Instana and provides a detailed analysis focusing on top problems,
|
|
69
|
+
their details, and actionable fix suggestions. You can specify a time range using timestamps or natural language
|
|
70
|
+
like "last 24 hours" or "last 2 days".
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
from_time: Start timestamp in milliseconds since epoch (optional)
|
|
74
|
+
to_time: End timestamp in milliseconds since epoch (optional)
|
|
75
|
+
time_range: Natural language time range like "last 24 hours", "last 2 days", "last week" (optional)
|
|
76
|
+
max_events: Maximum number of events to process (default: 50)
|
|
77
|
+
ctx: The MCP context (optional)
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Dictionary containing detailed Kubernetes events analysis or error information
|
|
81
|
+
"""
|
|
82
|
+
try:
|
|
83
|
+
# Process natural language time range if provided
|
|
84
|
+
if time_range:
|
|
85
|
+
print(f"DEBUG: Processing natural language time range: '{time_range}'", file=sys.stderr)
|
|
86
|
+
|
|
87
|
+
# Current time in milliseconds
|
|
88
|
+
current_time_ms = int(datetime.now().timestamp() * 1000)
|
|
89
|
+
|
|
90
|
+
# Default to 24 hours if just "last few hours" is specified
|
|
91
|
+
if time_range.lower() in ["last few hours", "last hours", "few hours"]:
|
|
92
|
+
hours = 24
|
|
93
|
+
from_time = current_time_ms - (hours * 60 * 60 * 1000)
|
|
94
|
+
to_time = current_time_ms
|
|
95
|
+
print(f"DEBUG: Interpreted as last {hours} hours", file=sys.stderr)
|
|
96
|
+
# Extract hours if specified
|
|
97
|
+
elif "hour" in time_range.lower():
|
|
98
|
+
import re
|
|
99
|
+
hour_match = re.search(r'(\d+)\s*hour', time_range.lower())
|
|
100
|
+
hours = int(hour_match.group(1)) if hour_match else 24
|
|
101
|
+
from_time = current_time_ms - (hours * 60 * 60 * 1000)
|
|
102
|
+
to_time = current_time_ms
|
|
103
|
+
# Extract days if specified
|
|
104
|
+
elif "day" in time_range.lower():
|
|
105
|
+
import re
|
|
106
|
+
day_match = re.search(r'(\d+)\s*day', time_range.lower())
|
|
107
|
+
days = int(day_match.group(1)) if day_match else 1
|
|
108
|
+
from_time = current_time_ms - (days * 24 * 60 * 60 * 1000)
|
|
109
|
+
to_time = current_time_ms
|
|
110
|
+
# Handle "last week"
|
|
111
|
+
elif "week" in time_range.lower():
|
|
112
|
+
import re
|
|
113
|
+
week_match = re.search(r'(\d+)\s*week', time_range.lower())
|
|
114
|
+
weeks = int(week_match.group(1)) if week_match else 1
|
|
115
|
+
from_time = current_time_ms - (weeks * 7 * 24 * 60 * 60 * 1000)
|
|
116
|
+
to_time = current_time_ms
|
|
117
|
+
# Handle "last month"
|
|
118
|
+
elif "month" in time_range.lower():
|
|
119
|
+
import re
|
|
120
|
+
month_match = re.search(r'(\d+)\s*month', time_range.lower())
|
|
121
|
+
months = int(month_match.group(1)) if month_match else 1
|
|
122
|
+
from_time = current_time_ms - (months * 30 * 24 * 60 * 60 * 1000)
|
|
123
|
+
to_time = current_time_ms
|
|
124
|
+
# Default to 24 hours for any other time range
|
|
125
|
+
else:
|
|
126
|
+
hours = 24
|
|
127
|
+
from_time = current_time_ms - (hours * 60 * 60 * 1000)
|
|
128
|
+
to_time = current_time_ms
|
|
129
|
+
|
|
130
|
+
# Set default time range if not provided
|
|
131
|
+
if not to_time:
|
|
132
|
+
to_time = int(datetime.now().timestamp() * 1000)
|
|
133
|
+
if not from_time:
|
|
134
|
+
from_time = to_time - (24 * 60 * 60 * 1000) # Default to 24 hours
|
|
135
|
+
|
|
136
|
+
# Call the kubernetes_info_events method from the SDK
|
|
137
|
+
result = self.events_api.kubernetes_info_events(
|
|
138
|
+
to=to_time,
|
|
139
|
+
var_from=from_time,
|
|
140
|
+
window_size=None,
|
|
141
|
+
filter_event_updates=None,
|
|
142
|
+
exclude_triggered_before=None
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Print the raw result for debugging
|
|
146
|
+
print(f"DEBUG: Raw API result type: {type(result)}", file=sys.stderr)
|
|
147
|
+
print(f"DEBUG: Raw API result length: {len(result) if isinstance(result, list) else 'not a list'}", file=sys.stderr)
|
|
148
|
+
|
|
149
|
+
# If there are no events, return early
|
|
150
|
+
if not result or (isinstance(result, list) and len(result) == 0):
|
|
151
|
+
from_date = datetime.fromtimestamp(from_time/1000).strftime('%Y-%m-%d %H:%M:%S')
|
|
152
|
+
to_date = datetime.fromtimestamp(to_time/1000).strftime('%Y-%m-%d %H:%M:%S')
|
|
153
|
+
return {
|
|
154
|
+
"analysis": f"No Kubernetes events found between {from_date} and {to_date}.",
|
|
155
|
+
"time_range": f"{from_date} to {to_date}",
|
|
156
|
+
"events_count": 0
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
# Process the events to create a summary
|
|
160
|
+
events = result if isinstance(result, list) else [result]
|
|
161
|
+
|
|
162
|
+
# Get the total number of events before limiting
|
|
163
|
+
total_events_count = len(events)
|
|
164
|
+
|
|
165
|
+
# Limit the number of events to process
|
|
166
|
+
events = events[:max_events]
|
|
167
|
+
print(f"DEBUG: Limited to processing {len(events)} events out of {total_events_count} total events", file=sys.stderr)
|
|
168
|
+
|
|
169
|
+
# Convert InfraEventResult objects to dictionaries if needed
|
|
170
|
+
event_dicts = []
|
|
171
|
+
for event in events:
|
|
172
|
+
if hasattr(event, 'to_dict'):
|
|
173
|
+
event_dicts.append(event.to_dict())
|
|
174
|
+
else:
|
|
175
|
+
event_dicts.append(event)
|
|
176
|
+
|
|
177
|
+
# Group events by problem type
|
|
178
|
+
problem_groups = {}
|
|
179
|
+
|
|
180
|
+
# Process each event
|
|
181
|
+
for event in event_dicts:
|
|
182
|
+
problem = event.get("problem", "Unknown")
|
|
183
|
+
|
|
184
|
+
# Initialize problem group if not exists
|
|
185
|
+
if problem not in problem_groups:
|
|
186
|
+
problem_groups[problem] = {
|
|
187
|
+
"count": 0,
|
|
188
|
+
"affected_namespaces": set(),
|
|
189
|
+
"affected_entities": set(),
|
|
190
|
+
"details": set(),
|
|
191
|
+
"fix_suggestions": set(),
|
|
192
|
+
"sample_events": []
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
# Update problem group
|
|
196
|
+
problem_groups[problem]["count"] += 1
|
|
197
|
+
|
|
198
|
+
# Extract namespace from entityLabel
|
|
199
|
+
entity_label = event.get("entityLabel", "")
|
|
200
|
+
if "/" in entity_label:
|
|
201
|
+
namespace, entity = entity_label.split("/", 1)
|
|
202
|
+
problem_groups[problem]["affected_namespaces"].add(namespace)
|
|
203
|
+
problem_groups[problem]["affected_entities"].add(entity)
|
|
204
|
+
|
|
205
|
+
# Add detail and fix suggestion
|
|
206
|
+
detail = event.get("detail", "")
|
|
207
|
+
if detail:
|
|
208
|
+
problem_groups[problem]["details"].add(detail)
|
|
209
|
+
|
|
210
|
+
fix_suggestion = event.get("fixSuggestion", "")
|
|
211
|
+
if fix_suggestion:
|
|
212
|
+
problem_groups[problem]["fix_suggestions"].add(fix_suggestion)
|
|
213
|
+
|
|
214
|
+
# Add sample event (up to 3 per problem)
|
|
215
|
+
if len(problem_groups[problem]["sample_events"]) < 3:
|
|
216
|
+
simple_event = {
|
|
217
|
+
"eventId": event.get("eventId", ""),
|
|
218
|
+
"start": event.get("start", 0),
|
|
219
|
+
"entityLabel": event.get("entityLabel", ""),
|
|
220
|
+
"detail": detail
|
|
221
|
+
}
|
|
222
|
+
problem_groups[problem]["sample_events"].append(simple_event)
|
|
223
|
+
|
|
224
|
+
# Sort problems by count (most frequent first)
|
|
225
|
+
sorted_problems = sorted(problem_groups.items(), key=lambda x: x[1]["count"], reverse=True)
|
|
226
|
+
|
|
227
|
+
# Format the time range in a human-readable format
|
|
228
|
+
from_date = datetime.fromtimestamp(from_time/1000).strftime('%Y-%m-%d %H:%M:%S')
|
|
229
|
+
to_date = datetime.fromtimestamp(to_time/1000).strftime('%Y-%m-%d %H:%M:%S')
|
|
230
|
+
|
|
231
|
+
# Create a detailed analysis of each problem
|
|
232
|
+
problem_analyses = []
|
|
233
|
+
|
|
234
|
+
# Process each problem
|
|
235
|
+
for problem_name, problem_data in sorted_problems:
|
|
236
|
+
# Create a detailed problem analysis
|
|
237
|
+
problem_analysis = {
|
|
238
|
+
"problem": problem_name,
|
|
239
|
+
"count": problem_data["count"],
|
|
240
|
+
"affected_namespaces": list(problem_data["affected_namespaces"]),
|
|
241
|
+
"details": list(problem_data["details"]),
|
|
242
|
+
"fix_suggestions": list(problem_data["fix_suggestions"]),
|
|
243
|
+
"sample_events": problem_data["sample_events"]
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
problem_analyses.append(problem_analysis)
|
|
247
|
+
|
|
248
|
+
# Create a comprehensive analysis
|
|
249
|
+
analysis_result = {
|
|
250
|
+
"summary": f"Analysis based on {len(events)} of {total_events_count} Kubernetes events between {from_date} and {to_date}.",
|
|
251
|
+
"time_range": f"{from_date} to {to_date}",
|
|
252
|
+
"events_count": total_events_count,
|
|
253
|
+
"events_analyzed": len(events),
|
|
254
|
+
"problem_analyses": problem_analyses[:10] # Limit to top 10 problems for readability
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
# Create a more user-friendly text summary for direct display
|
|
258
|
+
markdown_summary = "# Kubernetes Events Analysis\n\n"
|
|
259
|
+
markdown_summary += f"Analysis based on {len(events)} of {total_events_count} Kubernetes events between {from_date} and {to_date}.\n\n"
|
|
260
|
+
|
|
261
|
+
markdown_summary += "## Top Problems\n\n"
|
|
262
|
+
|
|
263
|
+
# Add each problem to the markdown summary
|
|
264
|
+
for problem_analysis in problem_analyses[:5]: # Limit to top 5 for readability
|
|
265
|
+
problem_name = problem_analysis["problem"]
|
|
266
|
+
count = problem_analysis["count"]
|
|
267
|
+
|
|
268
|
+
markdown_summary += f"### {problem_name} ({count} events)\n\n"
|
|
269
|
+
|
|
270
|
+
# Add affected namespaces if available
|
|
271
|
+
if problem_analysis.get("affected_namespaces"):
|
|
272
|
+
namespaces = ", ".join(problem_analysis["affected_namespaces"][:5])
|
|
273
|
+
if len(problem_analysis["affected_namespaces"]) > 5:
|
|
274
|
+
namespaces += f" and {len(problem_analysis['affected_namespaces']) - 5} more"
|
|
275
|
+
markdown_summary += f"**Affected Namespaces:** {namespaces}\n\n"
|
|
276
|
+
|
|
277
|
+
# Add fix suggestions
|
|
278
|
+
if problem_analysis.get("fix_suggestions"):
|
|
279
|
+
markdown_summary += "**Fix Suggestions:**\n\n"
|
|
280
|
+
for suggestion in list(problem_analysis["fix_suggestions"])[:3]: # Limit to top 3 suggestions
|
|
281
|
+
markdown_summary += f"- {suggestion}\n"
|
|
282
|
+
|
|
283
|
+
markdown_summary += "\n"
|
|
284
|
+
|
|
285
|
+
# Add the markdown summary to the result
|
|
286
|
+
analysis_result["markdown_summary"] = markdown_summary
|
|
287
|
+
|
|
288
|
+
return analysis_result
|
|
289
|
+
|
|
290
|
+
except Exception as e:
|
|
291
|
+
print(f"Error in get_kubernetes_info_events: {e}", file=sys.stderr)
|
|
292
|
+
import traceback
|
|
293
|
+
traceback.print_exc(file=sys.stderr)
|
|
294
|
+
return {
|
|
295
|
+
"error": f"Failed to get Kubernetes info events: {str(e)}"
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
@register_as_tool
|
|
299
|
+
async def get_agent_monitoring_events(self,
|
|
300
|
+
query: Optional[str] = None,
|
|
301
|
+
from_time: Optional[int] = None,
|
|
302
|
+
to_time: Optional[int] = None,
|
|
303
|
+
size: Optional[int] = 100,
|
|
304
|
+
max_events: Optional[int] = 50, # Added parameter to limit events
|
|
305
|
+
time_range: Optional[str] = None, # Added parameter for natural language time range
|
|
306
|
+
ctx=None) -> Dict[str, Any]:
|
|
307
|
+
"""
|
|
308
|
+
Get agent monitoring events from Instana and return a detailed analysis.
|
|
309
|
+
|
|
310
|
+
This tool retrieves agent monitoring events from Instana and provides a detailed analysis focusing on
|
|
311
|
+
monitoring issues, their frequency, and affected entities. You can specify a time range using timestamps
|
|
312
|
+
or natural language like "last 24 hours" or "last 2 days".
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
query: Query string to filter events (optional)
|
|
316
|
+
from_time: Start timestamp in milliseconds since epoch (optional, defaults to 1 hour ago)
|
|
317
|
+
to_time: End timestamp in milliseconds since epoch (optional, defaults to now)
|
|
318
|
+
size: Maximum number of events to return from API (optional, default 100)
|
|
319
|
+
max_events: Maximum number of events to process for analysis (optional, default 50)
|
|
320
|
+
time_range: Natural language time range like "last 24 hours", "last 2 days", "last week" (optional)
|
|
321
|
+
ctx: The MCP context (optional)
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
Dictionary containing summarized agent monitoring events data or error information
|
|
325
|
+
"""
|
|
326
|
+
try:
|
|
327
|
+
# Process natural language time range if provided
|
|
328
|
+
if time_range:
|
|
329
|
+
print(f"DEBUG: Processing natural language time range: '{time_range}'", file=sys.stderr)
|
|
330
|
+
|
|
331
|
+
# Current time in milliseconds
|
|
332
|
+
current_time_ms = int(datetime.now().timestamp() * 1000)
|
|
333
|
+
|
|
334
|
+
# Default to 24 hours if just "last few hours" is specified
|
|
335
|
+
if time_range.lower() in ["last few hours", "last hours", "few hours"]:
|
|
336
|
+
hours = 24
|
|
337
|
+
from_time = current_time_ms - (hours * 60 * 60 * 1000)
|
|
338
|
+
to_time = current_time_ms
|
|
339
|
+
print(f"DEBUG: Interpreted as last {hours} hours", file=sys.stderr)
|
|
340
|
+
# Extract hours if specified
|
|
341
|
+
elif "hour" in time_range.lower():
|
|
342
|
+
import re
|
|
343
|
+
hour_match = re.search(r'(\d+)\s*hour', time_range.lower())
|
|
344
|
+
hours = int(hour_match.group(1)) if hour_match else 24
|
|
345
|
+
from_time = current_time_ms - (hours * 60 * 60 * 1000)
|
|
346
|
+
to_time = current_time_ms
|
|
347
|
+
# Extract days if specified
|
|
348
|
+
elif "day" in time_range.lower():
|
|
349
|
+
import re
|
|
350
|
+
day_match = re.search(r'(\d+)\s*day', time_range.lower())
|
|
351
|
+
days = int(day_match.group(1)) if day_match else 1
|
|
352
|
+
from_time = current_time_ms - (days * 24 * 60 * 60 * 1000)
|
|
353
|
+
to_time = current_time_ms
|
|
354
|
+
# Handle "last week"
|
|
355
|
+
elif "week" in time_range.lower():
|
|
356
|
+
import re
|
|
357
|
+
week_match = re.search(r'(\d+)\s*week', time_range.lower())
|
|
358
|
+
weeks = int(week_match.group(1)) if week_match else 1
|
|
359
|
+
from_time = current_time_ms - (weeks * 7 * 24 * 60 * 60 * 1000)
|
|
360
|
+
to_time = current_time_ms
|
|
361
|
+
# Handle "last month"
|
|
362
|
+
elif "month" in time_range.lower():
|
|
363
|
+
import re
|
|
364
|
+
month_match = re.search(r'(\d+)\s*month', time_range.lower())
|
|
365
|
+
months = int(month_match.group(1)) if month_match else 1
|
|
366
|
+
from_time = current_time_ms - (months * 30 * 24 * 60 * 60 * 1000)
|
|
367
|
+
to_time = current_time_ms
|
|
368
|
+
# Default to 24 hours for any other time range
|
|
369
|
+
else:
|
|
370
|
+
hours = 24
|
|
371
|
+
from_time = current_time_ms - (hours * 60 * 60 * 1000)
|
|
372
|
+
to_time = current_time_ms
|
|
373
|
+
|
|
374
|
+
print(f"get_agent_monitoring_events called with query={query}, from_time={from_time}, to_time={to_time}, size={size}", file=sys.stderr)
|
|
375
|
+
|
|
376
|
+
# Set default time range if not provided
|
|
377
|
+
if not to_time:
|
|
378
|
+
to_time = int(datetime.now().timestamp() * 1000)
|
|
379
|
+
|
|
380
|
+
if not from_time:
|
|
381
|
+
from_time = to_time - (60 * 60 * 1000) # Default to 1 hour
|
|
382
|
+
|
|
383
|
+
# Call the agent_monitoring_events method from the SDK
|
|
384
|
+
result = self.events_api.agent_monitoring_events(
|
|
385
|
+
to=to_time,
|
|
386
|
+
var_from=from_time,
|
|
387
|
+
window_size=None,
|
|
388
|
+
filter_event_updates=None,
|
|
389
|
+
exclude_triggered_before=None
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
# Print the raw result for debugging
|
|
393
|
+
print(f"DEBUG: Raw API result type: {type(result)}", file=sys.stderr)
|
|
394
|
+
print(f"DEBUG: Raw API result length: {len(result) if isinstance(result, list) else 'not a list'}", file=sys.stderr)
|
|
395
|
+
|
|
396
|
+
# If there are no events, return early
|
|
397
|
+
if not result or (isinstance(result, list) and len(result) == 0):
|
|
398
|
+
from_date = datetime.fromtimestamp(from_time/1000).strftime('%Y-%m-%d %H:%M:%S')
|
|
399
|
+
to_date = datetime.fromtimestamp(to_time/1000).strftime('%Y-%m-%d %H:%M:%S')
|
|
400
|
+
return {
|
|
401
|
+
"analysis": f"No agent monitoring events found between {from_date} and {to_date}.",
|
|
402
|
+
"time_range": f"{from_date} to {to_date}",
|
|
403
|
+
"events_count": 0
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
# Process the events to create a summary
|
|
407
|
+
events = result if isinstance(result, list) else [result]
|
|
408
|
+
|
|
409
|
+
# Get the total number of events before limiting
|
|
410
|
+
total_events_count = len(events)
|
|
411
|
+
|
|
412
|
+
# Limit the number of events to process
|
|
413
|
+
events = events[:max_events]
|
|
414
|
+
print(f"DEBUG: Limited to processing {len(events)} events out of {total_events_count} total events", file=sys.stderr)
|
|
415
|
+
|
|
416
|
+
# Convert objects to dictionaries if needed
|
|
417
|
+
event_dicts = []
|
|
418
|
+
for event in events:
|
|
419
|
+
if hasattr(event, 'to_dict'):
|
|
420
|
+
event_dicts.append(event.to_dict())
|
|
421
|
+
else:
|
|
422
|
+
event_dicts.append(event)
|
|
423
|
+
|
|
424
|
+
# Group events by problem type
|
|
425
|
+
problem_groups = {}
|
|
426
|
+
|
|
427
|
+
# Process each event
|
|
428
|
+
for event in event_dicts:
|
|
429
|
+
# Extract the monitoring issue from the problem field
|
|
430
|
+
full_problem = event.get("problem", "Unknown")
|
|
431
|
+
# Strip "Monitoring issue: " prefix if present
|
|
432
|
+
problem = full_problem.replace("Monitoring issue: ", "") if "Monitoring issue: " in full_problem else full_problem
|
|
433
|
+
|
|
434
|
+
# Initialize problem group if not exists
|
|
435
|
+
if problem not in problem_groups:
|
|
436
|
+
problem_groups[problem] = {
|
|
437
|
+
"count": 0,
|
|
438
|
+
"affected_entities": set(),
|
|
439
|
+
"entity_types": set(),
|
|
440
|
+
"sample_events": []
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
# Update problem group
|
|
444
|
+
problem_groups[problem]["count"] += 1
|
|
445
|
+
|
|
446
|
+
# Add entity information
|
|
447
|
+
entity_name = event.get("entityName", "Unknown")
|
|
448
|
+
entity_label = event.get("entityLabel", "Unknown")
|
|
449
|
+
entity_type = event.get("entityType", "Unknown")
|
|
450
|
+
|
|
451
|
+
entity_info = f"{entity_name} ({entity_label})"
|
|
452
|
+
problem_groups[problem]["affected_entities"].add(entity_info)
|
|
453
|
+
problem_groups[problem]["entity_types"].add(entity_type)
|
|
454
|
+
|
|
455
|
+
# Add sample event (up to 3 per problem)
|
|
456
|
+
if len(problem_groups[problem]["sample_events"]) < 3:
|
|
457
|
+
simple_event = {
|
|
458
|
+
"eventId": event.get("eventId", ""),
|
|
459
|
+
"start": event.get("start", 0),
|
|
460
|
+
"entityName": entity_name,
|
|
461
|
+
"entityLabel": entity_label,
|
|
462
|
+
"severity": event.get("severity", 0)
|
|
463
|
+
}
|
|
464
|
+
problem_groups[problem]["sample_events"].append(simple_event)
|
|
465
|
+
|
|
466
|
+
# Sort problems by count (most frequent first)
|
|
467
|
+
sorted_problems = sorted(problem_groups.items(), key=lambda x: x[1]["count"], reverse=True)
|
|
468
|
+
|
|
469
|
+
# Format the time range in a human-readable format
|
|
470
|
+
from_date = datetime.fromtimestamp(from_time/1000).strftime('%Y-%m-%d %H:%M:%S')
|
|
471
|
+
to_date = datetime.fromtimestamp(to_time/1000).strftime('%Y-%m-%d %H:%M:%S')
|
|
472
|
+
|
|
473
|
+
# Create a detailed analysis of each problem
|
|
474
|
+
problem_analyses = []
|
|
475
|
+
|
|
476
|
+
# Process each problem
|
|
477
|
+
for problem_name, problem_data in sorted_problems:
|
|
478
|
+
# Create a detailed problem analysis
|
|
479
|
+
problem_analysis = {
|
|
480
|
+
"problem": problem_name,
|
|
481
|
+
"count": problem_data["count"],
|
|
482
|
+
"affected_entities": list(problem_data["affected_entities"]),
|
|
483
|
+
"entity_types": list(problem_data["entity_types"]),
|
|
484
|
+
"sample_events": problem_data["sample_events"]
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
problem_analyses.append(problem_analysis)
|
|
488
|
+
|
|
489
|
+
# Create a comprehensive analysis
|
|
490
|
+
analysis_result = {
|
|
491
|
+
"summary": f"Analysis based on {len(events)} of {total_events_count} agent monitoring events between {from_date} and {to_date}.",
|
|
492
|
+
"time_range": f"{from_date} to {to_date}",
|
|
493
|
+
"events_count": total_events_count,
|
|
494
|
+
"events_analyzed": len(events),
|
|
495
|
+
"problem_analyses": problem_analyses[:10] # Limit to top 10 problems for readability
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
# Create a more user-friendly text summary for direct display
|
|
499
|
+
markdown_summary = "# Agent Monitoring Events Analysis\n\n"
|
|
500
|
+
markdown_summary += f"Analysis based on {len(events)} of {total_events_count} agent monitoring events between {from_date} and {to_date}.\n\n"
|
|
501
|
+
|
|
502
|
+
markdown_summary += "## Top Monitoring Issues\n\n"
|
|
503
|
+
|
|
504
|
+
# Add each problem to the markdown summary
|
|
505
|
+
for problem_analysis in problem_analyses[:5]: # Limit to top 5 for readability
|
|
506
|
+
problem_name = problem_analysis["problem"]
|
|
507
|
+
count = problem_analysis["count"]
|
|
508
|
+
|
|
509
|
+
markdown_summary += f"### {problem_name} ({count} events)\n\n"
|
|
510
|
+
|
|
511
|
+
# Add affected entities if available
|
|
512
|
+
if problem_analysis.get("affected_entities"):
|
|
513
|
+
entities = ", ".join(problem_analysis["affected_entities"][:5])
|
|
514
|
+
if len(problem_analysis["affected_entities"]) > 5:
|
|
515
|
+
entities += f" and {len(problem_analysis['affected_entities']) - 5} more"
|
|
516
|
+
markdown_summary += f"**Affected Entities:** {entities}\n\n"
|
|
517
|
+
|
|
518
|
+
markdown_summary += "\n"
|
|
519
|
+
|
|
520
|
+
# Add the markdown summary to the result
|
|
521
|
+
analysis_result["markdown_summary"] = markdown_summary
|
|
522
|
+
|
|
523
|
+
return analysis_result
|
|
524
|
+
|
|
525
|
+
except Exception as e:
|
|
526
|
+
print(f"Error in get_agent_monitoring_events: {e}", file=sys.stderr)
|
|
527
|
+
import traceback
|
|
528
|
+
traceback.print_exc(file=sys.stderr)
|
|
529
|
+
return {
|
|
530
|
+
"error": f"Failed to get agent monitoring events: {str(e)}"
|
|
531
|
+
}
|