Identifying Bottleneck Plugins

The crude script will loop through each plugin and check load time. See here for an example output: https://app.warp.dev/block/P0mRiYKGhE2B6wQAD7q2x2.

function find_slow_plugin() {
  _get_wp_execution_time() {
    local output
    local extracted_time

    # Capture stderr (where --debug output goes) to stdout.
    # 'command wp' ensures we call the actual wp binary, not an alias.
    output=$(command wp "$@" --debug 2>&1)

    # Perl script to extract the time like "0.123s". It handles debug formats
    # like "(0.123s)" or "(0.123s|more_info)".
    extracted_time=$(echo "$output" | perl -ne '/Debug \(bootstrap\): Running command: .+\(([^s]+s)/ && print $1')

    if [[ -n "$extracted_time" ]]; then
      echo "$extracted_time"
    else
      echo "" # Return empty on failure
    fi
  }

  local base_time_s
  local time_no_themes_s
  local time_no_plugins_s
  local time_with_skip_s
  local active_plugins # Array to hold active plugin slugs
  local plugin # Loop variable for plugin slug
  local results=() # Array to store results for sorting

  # Check 1: Is WP-CLI installed?
  if ! command -v wp &>/dev/null; then
    echo "❌ Error: WP-CLI (wp command) not found. Please install WP-CLI." >&2
    echo "See: https://wp-cli.org/" >&2
    return 1
  fi

  # Check 2: Are we in a WordPress directory?
  if ! command wp core is-installed --quiet; then
    echo "❌ Error: This does not appear to be a WordPress installation, or WP-CLI cannot connect." >&2
    echo "Please run this script from the root directory of a WordPress site." >&2
    return 1
  fi

  echo "🚀 WordPress Plugin Performance Test 🚀"
  echo "This script measures the execution time of 'wp plugin list --debug' under various conditions."
  echo ""

  echo "📋 Initial Baseline Measurements for 'wp plugin list --debug':"
  
  printf "  âŗ Measuring time with NO themes loaded (--skip-themes)... "
  time_no_themes_s=$(_get_wp_execution_time plugin list --skip-themes)
  if [[ -n "$time_no_themes_s" ]]; then
    echo "Time: $time_no_themes_s"
  else
    echo "âš ī¸  Could not measure (check 'wp plugin list --skip-themes --debug' manually)."
  fi

  printf "  âŗ Measuring time with NO plugins loaded (--skip-plugins)... "
  time_no_plugins_s=$(_get_wp_execution_time plugin list --skip-plugins)
  if [[ -n "$time_no_plugins_s" ]]; then
    echo "Time: $time_no_plugins_s"
  else
    echo "âš ī¸  Could not measure (check 'wp plugin list --skip-plugins --debug' manually)."
  fi
  
  printf "  âŗ Measuring base time (ALL plugins & theme active)... "
  base_time_s=$(_get_wp_execution_time plugin list)
  if [[ -z "$base_time_s" ]]; then
    echo "❌ Error: Could not measure base execution time (all plugins & theme active)." >&2
    echo "Please ensure WP-CLI is working correctly. Try 'wp plugin list --debug' manually." >&2
    return 1
  fi
  echo "Base time: $base_time_s"
  echo ""

  # Get a list of active plugin slugs.
  # 'mapfile' reads lines into an array, which is robust.
  mapfile -t active_plugins < <(command wp plugin list --field=name --status=active)

  if [[ ${#active_plugins[@]} -eq 0 ]]; then
    echo "â„šī¸ No active plugins found to test for individual impact."
    echo "✅ Test Complete (baselines reported above)."
    return 0 
  fi

  echo "📊 Measuring impact of individual plugins (compared to '$base_time_s' base time):"
  echo "A larger positive 'Impact' suggests the plugin contributes more to the load time of this specific WP-CLI command."
  echo "---------------------------------------------------------------------------------"
  printf "%-40s | %-15s | %-15s\n" "Plugin Skipped" "Time w/ Skip" "Impact (Base-Skip)"
  echo "---------------------------------------------------------------------------------"

  for plugin in "${active_plugins[@]}"; do
    time_with_skip_s=$(_get_wp_execution_time plugin list --skip-plugins="$plugin")

    if [[ -n "$time_with_skip_s" ]]; then
      # Remove 's' suffix for calculations
      local base_num=${base_time_s%s}
      local skip_num=${time_with_skip_s%s}

      # Use awk for floating-point arithmetic
      local diff_s
      diff_s=$(awk -v base="$base_num" -v skip="$skip_num" 'BEGIN { printf "%.3f", base - skip }')
      
      local impact_sign=""
      # Check if diff is positive using a robust numeric comparison
      if [[ $(awk -v diff="$diff_s" 'BEGIN { print (diff > 0) }') -eq 1 ]]; then
        impact_sign="+"
      fi
      
      # Store results for sorting later
      results+=("$(printf "%.3f" "$diff_s")|$plugin|$time_with_skip_s|${impact_sign}${diff_s}s")
    else
      # Store error result if time couldn't be measured
      results+=("0.000|$plugin|Error|Error measuring")
    fi
  done

  # Sort results by the numeric impact value (first field) in descending order
  mapfile -t sorted_results < <(for res in "${results[@]}"; do echo "$res"; done | sort -t'|' -k1,1nr)

  # Display sorted results
  for result_line in "${sorted_results[@]}"; do
    local p_name=$(echo "$result_line" | cut -d'|' -f2)
    local t_skip=$(echo "$result_line" | cut -d'|' -f3)
    local i_str=$(echo "$result_line" | cut -d'|' -f4)
    printf "%-40s | %-15s | %-15s\n" "$p_name" "$t_skip" "$i_str"
  done

  echo "---------------------------------------------------------------------------------"
  echo ""
  echo "✅ Test Complete"
  echo ""
  echo "💡 Note: This script measures impact on 'wp plugin list --debug' command execution time."
  echo "Actual impact on front-end or admin page load times may vary. For comprehensive"
  echo "profiling, consider tools like Query Monitor (plugin) or Xdebug."
  echo ""
}
find_slow_plugin