Skip to main content
Glama
isdaniel

PostgreSQL-Performance-Tuner-Mcp

analyze_wait_events

Identify PostgreSQL performance bottlenecks by analyzing wait events for client backend processes, helping detect I/O issues, lock contention, and resource saturation in application queries.

Instructions

Analyze PostgreSQL wait events to identify bottlenecks.

Note: This tool focuses on client backend processes and excludes system background processes to help identify bottlenecks in your application queries.

Wait events indicate what processes are waiting for:

  • Lock: Waiting for locks on tables/rows

  • IO: Waiting for disk I/O

  • CPU: Waiting for CPU time

  • Client: Waiting for client communication

  • Extension: Waiting in extension code

This helps identify:

  • I/O bottlenecks

  • Lock contention patterns

  • Resource saturation

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
active_onlyNoOnly include active (running) queries

Implementation Reference

  • The run_tool method in WaitEventsToolHandler class that implements the core logic of the 'analyze_wait_events' tool. It queries pg_stat_activity for current wait events on client backends, groups them by type and event, analyzes for issues like lock contention or I/O waits, and returns structured JSON output with recommendations.
    async def run_tool(self, arguments: dict[str, Any]) -> Sequence[TextContent]:
        try:
            active_only = arguments.get("active_only", True)
    
            # Get user filter for excluding specific user IDs
            user_filter = get_user_filter()
            activity_filter = user_filter.get_activity_filter()
    
            state_filter = "AND state = 'active'" if active_only else ""
    
            # Current wait events
            query = f"""
                SELECT
                    wait_event_type,
                    wait_event,
                    COUNT(*) as count,
                    array_agg(DISTINCT pid) as pids
                FROM pg_stat_activity
                WHERE wait_event IS NOT NULL
                  AND backend_type = 'client backend'
                  {state_filter}
                  {activity_filter}
                GROUP BY wait_event_type, wait_event
                ORDER BY count DESC
            """
    
            result = await self.sql_driver.execute_query(query)
    
            # Detailed breakdown by wait type
            type_query = f"""
                SELECT
                    wait_event_type,
                    COUNT(*) as count
                FROM pg_stat_activity
                WHERE wait_event_type IS NOT NULL
                  AND backend_type = 'client backend'
                  {state_filter}
                  {activity_filter}
                GROUP BY wait_event_type
                ORDER BY count DESC
            """
    
            type_result = await self.sql_driver.execute_query(type_query)
    
            analysis = {
                "issues": [],
                "recommendations": []
            }
    
            # Analyze wait types
            if type_result:
                for row in type_result:
                    wait_type = row.get("wait_event_type")
                    count = row.get("count", 0)
    
                    if wait_type == "Lock" and count > 5:
                        analysis["issues"].append(f"{count} processes waiting on locks")
                        analysis["recommendations"].append(
                            "Investigate lock contention using pg_locks and pg_blocking_pids()"
                        )
                    elif wait_type == "IO" and count > 10:
                        analysis["issues"].append(f"{count} processes waiting on I/O")
                        analysis["recommendations"].append(
                            "Consider tuning I/O settings or increasing shared_buffers"
                        )
                    elif wait_type == "BufferPin" and count > 0:
                        analysis["issues"].append(f"{count} processes waiting on buffer pins")
                        analysis["recommendations"].append(
                            "This may indicate contention on frequently accessed pages"
                        )
    
            output = {
                "wait_events": result,
                "by_type": type_result,
                "analysis": analysis
            }
    
            return self.format_json_result(output)
    
        except Exception as e:
            return self.format_error(e)
  • The get_tool_definition method defining the tool schema, including name 'analyze_wait_events', description, input schema with 'active_only' parameter, and annotations.
    def get_tool_definition(self) -> Tool:
        return Tool(
            name=self.name,
            description=self.description,
            inputSchema={
                "type": "object",
                "properties": {
                    "active_only": {
                        "type": "boolean",
                        "description": "Only include active (running) queries",
                        "default": True
                    }
                },
                "required": []
            },
            annotations=self.get_annotations()
        )
  • The complete WaitEventsToolHandler class that defines and implements the 'analyze_wait_events' tool, including name, description, schema, and execution logic.
    class WaitEventsToolHandler(ToolHandler):
        """Tool handler for analyzing wait events."""
    
        name = "analyze_wait_events"
        title = "Wait Events Analyzer"
        read_only_hint = True
        destructive_hint = False
        idempotent_hint = True
        open_world_hint = False
        description = """Analyze PostgreSQL wait events to identify bottlenecks.
    
    Note: This tool focuses on client backend processes and excludes system
    background processes to help identify bottlenecks in your application queries.
    
    Wait events indicate what processes are waiting for:
    - Lock: Waiting for locks on tables/rows
    - IO: Waiting for disk I/O
    - CPU: Waiting for CPU time
    - Client: Waiting for client communication
    - Extension: Waiting in extension code
    
    This helps identify:
    - I/O bottlenecks
    - Lock contention patterns
    - Resource saturation"""
    
        def __init__(self, sql_driver: SqlDriver):
            self.sql_driver = sql_driver
    
        def get_tool_definition(self) -> Tool:
            return Tool(
                name=self.name,
                description=self.description,
                inputSchema={
                    "type": "object",
                    "properties": {
                        "active_only": {
                            "type": "boolean",
                            "description": "Only include active (running) queries",
                            "default": True
                        }
                    },
                    "required": []
                },
                annotations=self.get_annotations()
            )
    
        async def run_tool(self, arguments: dict[str, Any]) -> Sequence[TextContent]:
            try:
                active_only = arguments.get("active_only", True)
    
                # Get user filter for excluding specific user IDs
                user_filter = get_user_filter()
                activity_filter = user_filter.get_activity_filter()
    
                state_filter = "AND state = 'active'" if active_only else ""
    
                # Current wait events
                query = f"""
                    SELECT
                        wait_event_type,
                        wait_event,
                        COUNT(*) as count,
                        array_agg(DISTINCT pid) as pids
                    FROM pg_stat_activity
                    WHERE wait_event IS NOT NULL
                      AND backend_type = 'client backend'
                      {state_filter}
                      {activity_filter}
                    GROUP BY wait_event_type, wait_event
                    ORDER BY count DESC
                """
    
                result = await self.sql_driver.execute_query(query)
    
                # Detailed breakdown by wait type
                type_query = f"""
                    SELECT
                        wait_event_type,
                        COUNT(*) as count
                    FROM pg_stat_activity
                    WHERE wait_event_type IS NOT NULL
                      AND backend_type = 'client backend'
                      {state_filter}
                      {activity_filter}
                    GROUP BY wait_event_type
                    ORDER BY count DESC
                """
    
                type_result = await self.sql_driver.execute_query(type_query)
    
                analysis = {
                    "issues": [],
                    "recommendations": []
                }
    
                # Analyze wait types
                if type_result:
                    for row in type_result:
                        wait_type = row.get("wait_event_type")
                        count = row.get("count", 0)
    
                        if wait_type == "Lock" and count > 5:
                            analysis["issues"].append(f"{count} processes waiting on locks")
                            analysis["recommendations"].append(
                                "Investigate lock contention using pg_locks and pg_blocking_pids()"
                            )
                        elif wait_type == "IO" and count > 10:
                            analysis["issues"].append(f"{count} processes waiting on I/O")
                            analysis["recommendations"].append(
                                "Consider tuning I/O settings or increasing shared_buffers"
                            )
                        elif wait_type == "BufferPin" and count > 0:
                            analysis["issues"].append(f"{count} processes waiting on buffer pins")
                            analysis["recommendations"].append(
                                "This may indicate contention on frequently accessed pages"
                            )
    
                output = {
                    "wait_events": result,
                    "by_type": type_result,
                    "analysis": analysis
                }
    
                return self.format_json_result(output)
    
            except Exception as e:
                return self.format_error(e)
  • The register_all_tools function where WaitEventsToolHandler is instantiated with the SQL driver and registered via add_tool_handler at line 161. This central registry adds the tool to the MCP server's tool_handlers dict.
    def register_all_tools() -> None:
        """
        Register all available tool handlers.
    
        This function serves as the central registry for all tools.
        New tool handlers should be added here for automatic registration.
        """
        driver = get_sql_driver()
        hypopg_service = HypoPGService(driver)
        index_advisor = IndexAdvisor(driver)
    
        # Performance analysis tools
        add_tool_handler(GetSlowQueriesToolHandler(driver))
        add_tool_handler(AnalyzeQueryToolHandler(driver))
        add_tool_handler(TableStatsToolHandler(driver))
    
        # Index tuning tools
        add_tool_handler(IndexAdvisorToolHandler(index_advisor))
        add_tool_handler(ExplainQueryToolHandler(driver, hypopg_service))
        add_tool_handler(HypoPGToolHandler(hypopg_service))
        add_tool_handler(UnusedIndexesToolHandler(driver))
    
        # Database health tools
        add_tool_handler(DatabaseHealthToolHandler(driver))
        add_tool_handler(ActiveQueriesToolHandler(driver))
        add_tool_handler(WaitEventsToolHandler(driver))
        add_tool_handler(DatabaseSettingsToolHandler(driver))
    
        # Bloat detection tools (using pgstattuple extension)
        add_tool_handler(TableBloatToolHandler(driver))
        add_tool_handler(IndexBloatToolHandler(driver))
        add_tool_handler(DatabaseBloatSummaryToolHandler(driver))

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/isdaniel/pgtuner-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server