2024-08-18 18:54:40 -05:00
|
|
|
# tools/metrics_tool.py
|
|
|
|
|
from .base_tool import BaseTool
|
2025-06-02 17:05:43 -05:00
|
|
|
from .metrics import metrics as global_metrics_instance # For default and measuring execute
|
|
|
|
|
from .metrics import Metrics # For type hinting and potentially creating a new one if needed
|
|
|
|
|
import logging
|
2024-08-18 18:54:40 -05:00
|
|
|
|
|
|
|
|
class MetricsTool(BaseTool):
|
2025-06-02 17:05:43 -05:00
|
|
|
def __init__(self, metrics_provider: Metrics | None = None, logger: logging.Logger | None = None):
|
|
|
|
|
self.metrics_provider = metrics_provider if metrics_provider is not None else global_metrics_instance
|
|
|
|
|
self.logger = logger if logger else logging.getLogger(__name__)
|
|
|
|
|
if not self.logger.handlers:
|
|
|
|
|
self.logger.addHandler(logging.NullHandler())
|
|
|
|
|
self.logger.debug(f"MetricsTool initialized. Using metrics provider: {self.metrics_provider}")
|
2024-08-18 18:54:40 -05:00
|
|
|
|
|
|
|
|
def clear(self):
|
2025-06-02 17:05:43 -05:00
|
|
|
# This tool itself doesn't hold state that needs clearing beyond what its metrics_provider might do.
|
|
|
|
|
# If this tool were responsible for clearing the metrics it reports on, it would call:
|
|
|
|
|
# self.metrics_provider.clear_metrics()
|
|
|
|
|
self.logger.debug("MetricsTool clear method called. No local state to clear.")
|
2024-08-18 18:54:40 -05:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def get_functions(self):
|
|
|
|
|
return [
|
|
|
|
|
{
|
2025-06-01 11:50:12 -05:00
|
|
|
"type": "function",
|
|
|
|
|
"function": {
|
|
|
|
|
"name": "get_function_metrics",
|
|
|
|
|
"description": "Get metrics for all measured functions.",
|
|
|
|
|
"parameters": {
|
2024-08-18 18:54:40 -05:00
|
|
|
"type": "object",
|
|
|
|
|
"properties": {},
|
|
|
|
|
"required": []
|
2025-06-01 11:50:12 -05:00
|
|
|
}
|
2024-08-18 18:54:40 -05:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
2025-06-01 11:50:12 -05:00
|
|
|
"type": "function",
|
|
|
|
|
"function": {
|
|
|
|
|
"name": "get_specific_function_metrics",
|
|
|
|
|
"description": "Get metrics for a specific function.",
|
|
|
|
|
"parameters": {
|
2024-08-18 18:54:40 -05:00
|
|
|
"type": "object",
|
|
|
|
|
"properties": {
|
|
|
|
|
"function_name": {
|
2025-06-01 11:50:12 -05:00
|
|
|
"type": "string",
|
|
|
|
|
"description": "Name of the function to get metrics for"
|
2024-08-18 18:54:40 -05:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"required": ["function_name"]
|
2025-06-01 11:50:12 -05:00
|
|
|
}
|
2024-08-18 18:54:40 -05:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
2025-06-01 11:50:12 -05:00
|
|
|
"type": "function",
|
|
|
|
|
"function": {
|
|
|
|
|
"name": "get_top_n_functions",
|
|
|
|
|
"description": "Get the top N functions by total execution time.",
|
|
|
|
|
"parameters": {
|
2024-08-18 18:54:40 -05:00
|
|
|
"type": "object",
|
|
|
|
|
"properties": {
|
|
|
|
|
"n": {
|
2025-06-01 11:50:12 -05:00
|
|
|
"type": "integer",
|
|
|
|
|
"description": "Number of top functions to retrieve"
|
2024-08-18 18:54:40 -05:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"required": ["n"]
|
2025-06-01 11:50:12 -05:00
|
|
|
}
|
2024-08-18 18:54:40 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
|
2025-06-02 17:05:43 -05:00
|
|
|
@global_metrics_instance.measure # The execute method can be measured by the global instance
|
2024-08-18 18:54:40 -05:00
|
|
|
def execute(self, function_name, **kwargs):
|
2025-06-02 17:05:43 -05:00
|
|
|
self.logger.info(f"Executing MetricsTool function: {function_name} with args: {kwargs}")
|
2024-08-18 18:54:40 -05:00
|
|
|
if function_name == "get_function_metrics":
|
|
|
|
|
return self._get_function_metrics()
|
|
|
|
|
elif function_name == "get_specific_function_metrics":
|
2025-06-02 17:05:43 -05:00
|
|
|
func_name_arg = kwargs.get("function_name")
|
|
|
|
|
if func_name_arg is None: # Check if None, as empty string could be a valid (though unlikely) func name
|
|
|
|
|
self.logger.warning("'function_name' argument is missing for get_specific_function_metrics.")
|
|
|
|
|
return "Error: Missing required argument 'function_name'."
|
|
|
|
|
return self._get_specific_function_metrics(str(func_name_arg)) # Ensure string
|
2024-08-18 18:54:40 -05:00
|
|
|
elif function_name == "get_top_n_functions":
|
2025-06-02 17:05:43 -05:00
|
|
|
n_arg = kwargs.get("n")
|
|
|
|
|
if n_arg is None:
|
|
|
|
|
self.logger.warning("'n' argument is missing for get_top_n_functions.")
|
|
|
|
|
return "Error: Missing required argument 'n'."
|
|
|
|
|
try:
|
|
|
|
|
n_val = int(n_arg)
|
|
|
|
|
if n_val <= 0:
|
|
|
|
|
self.logger.warning(f"'n' argument must be a positive integer, got {n_val}.")
|
|
|
|
|
return "Error: Argument 'n' must be a positive integer."
|
|
|
|
|
return self._get_top_n_functions(n_val)
|
|
|
|
|
except ValueError:
|
|
|
|
|
self.logger.warning(f"'n' argument must be an integer, got '{n_arg}'.")
|
|
|
|
|
return "Error: Argument 'n' must be an integer."
|
2024-08-18 18:54:40 -05:00
|
|
|
else:
|
2025-06-02 17:05:43 -05:00
|
|
|
error_message = f"Unknown function: {function_name}"
|
|
|
|
|
self.logger.error(error_message)
|
|
|
|
|
return error_message
|
2024-08-18 18:54:40 -05:00
|
|
|
|
|
|
|
|
def _get_function_metrics(self):
|
2025-06-02 17:05:43 -05:00
|
|
|
self.logger.debug("Calling metrics_provider.get_metrics() for all functions.")
|
|
|
|
|
return self.metrics_provider.get_metrics()
|
2024-08-18 18:54:40 -05:00
|
|
|
|
2025-06-02 17:05:43 -05:00
|
|
|
def _get_specific_function_metrics(self, function_to_get):
|
|
|
|
|
self.logger.debug(f"Getting metrics for specific function: {function_to_get}")
|
|
|
|
|
all_metrics = self.metrics_provider.get_metrics()
|
|
|
|
|
return all_metrics.get(function_to_get, f"No metrics found for function: {function_to_get}")
|
2024-08-18 18:54:40 -05:00
|
|
|
|
|
|
|
|
def _get_top_n_functions(self, n):
|
2025-06-02 17:05:43 -05:00
|
|
|
self.logger.debug(f"Getting top {n} functions by total execution time.")
|
|
|
|
|
all_metrics = self.metrics_provider.get_metrics()
|
|
|
|
|
# Ensure that the items are actual metric dicts before trying to access 'total_time'
|
|
|
|
|
valid_metrics_items = []
|
|
|
|
|
for name, metric_values in all_metrics.items():
|
|
|
|
|
if isinstance(metric_values, dict) and 'total_time' in metric_values:
|
|
|
|
|
valid_metrics_items.append((name, metric_values))
|
|
|
|
|
else:
|
|
|
|
|
self.logger.warning(f"Metric item for '{name}' is not in expected format: {metric_values}")
|
|
|
|
|
|
|
|
|
|
# Sort items by total_time. items() gives list of (func_name, metrics_dict)
|
|
|
|
|
try:
|
|
|
|
|
sorted_metrics = sorted(valid_metrics_items, key=lambda item: item[1]['total_time'], reverse=True)
|
|
|
|
|
return dict(sorted_metrics[:n])
|
|
|
|
|
except TypeError as e:
|
|
|
|
|
self.logger.error(f"Error sorting metrics, possibly due to unexpected data types: {e}", exc_info=True)
|
|
|
|
|
return "Error: Could not sort metrics due to unexpected data."
|