#!/bin/bash

# /*******************************************************************************
# Copyright (C) 2024 Intel Corporation.
# This software and the related documents are Intel copyrighted materials,
# and your use of them is governed by the express license under which they
# were provided to you (License). Unless the License provides otherwise,
# you may not use, modify, copy, publish, distribute, disclose or transmit
# this software or the related documents without Intel's prior written
# permission. This software and the related documents are provided as is,
# with no express or implied warranties, other than those that are expressly
# stated in the License.
#
# *******************************************************************************/

# Define minimum level-zero version
LEVEL_ZERO_MIN_VERSION="1.3.27642"
SCRIPT_VERSION="0.5.0"

# Get machine name and current time
machine_name=$(hostname)
current_time=$(date +'%Y-%m-%d_%H-%M-%S_%3N')

# Define initial logfile name components
logfile_prefix="debugger_check_${machine_name}_${current_time}"
verbose=false
list=false
total_checks=0
passed_checks=0
failed_checks=0
warning_checks=0
header_printed=false

# Print help message
print_help() {
    echo "Usage: $0 [options]"
    echo "Options:"
    echo "  -v, --verbose       Enable verbose mode with detailed output"
    echo "  -L, --logfile FILE  Specify a log file (default: debugger_check_MACHINE_NAME_CURRENT_TIME.log)"
    echo "  -l, --list          List all available checks"
    echo "  -h, --help          Show this help message and exit"
}

# Parse arguments
while [[ $# -gt 0 ]]; do
    case $1 in
        -v|--verbose)
            verbose=true
            logfile_prefix="${logfile_prefix}_verbose"
            shift
            ;;
        -L|--logfile)
            logfile=$2
            shift 2
            ;;
        -l|--list)
            list=true
            logfile_prefix="${logfile_prefix}_list"
            shift
            ;;
        -h|--help)
            print_help
            exit 0
            ;;
        *)
            echo "Error: Invalid option '$1'"
            print_help
            exit 1
            ;;
    esac
done

# Finalize logfile name if not provided
logfile="${logfile:-${logfile_prefix}.log}"

# Function to log messages
log_message() {
    local message=$1
    echo -e "$message" | tee -a "$logfile"
}

# Function to log check results
log_check_result() {
    total_checks=$((total_checks + 1))

    local check_name=$1
    local check_description=$2
    local result_status=$3
    local details=$4

    if [[ "$header_printed" == false ]]; then
        if [[ "$verbose" == true ]]; then
            echo "Check Name         | Description                              | Result Status | Details" | tee -a "$logfile"
            echo "------------------ | ---------------------------------------- | ------------- | --------------------------------" | tee -a "$logfile"
        else
            echo "Check Name         | Result Status | Details" | tee -a "$logfile"
            echo "------------------ | ------------- | --------------------------------" | tee -a "$logfile"
        fi
        header_printed=true
    fi

    if [[ "$result_status" == "PASS" ]]; then
        passed_checks=$((passed_checks + 1))
    elif [[ "$result_status" == "FAIL" ]]; then
        failed_checks=$((failed_checks + 1))
    elif [[ "$result_status" == "WARNING" ]]; then
        warning_checks=$((warning_checks + 1))
    fi

    if [[ "$verbose" == true ]]; then
        printf "%-18s | %-40s | %-13s | %-30s\n" "$check_name" "$check_description" "$result_status" "$details" | tee -a "$logfile"
    else
        if [[ "$result_status" != "PASS" ]]; then
            printf "%-18s | %-13s | %-30s\n" "$check_name" "$result_status" "$details" | tee -a "$logfile"
        fi
    fi
}

# Function to get OS information
get_OS() {
    uname -s
}

# Function to get GDB base directory
get_gdb_base_dir() {
    gdb_bin_path=$(which gdb-oneapi)
    if [[ -n "$gdb_bin_path" ]]; then
        gdb_bin_dir=$(dirname "$gdb_bin_path")
        echo "$(dirname "$gdb_bin_dir")"
    else
        echo ""
    fi
}

# Function to check if a file exists in GDB directory
check_file_in_gdb_dir() {
    local rel_path=$1
    local component_name=$2
    local version_info=$3
    local check_name="${component_name} Check"
    local check_description="Checking for $component_name in GDB directory"

    if [[ "$list" == true ]]; then
        printf "%-18s | %-40s\n" "$check_name" "$check_description" | tee -a "$logfile"
        return
    fi

    gdb_base_dir=$(get_gdb_base_dir)
    if [[ -n "$gdb_base_dir" && -f "$gdb_base_dir/$rel_path" ]]; then
        log_check_result "$check_name" "$check_description" "PASS" "Version: ${version_info}"
    else
        log_check_result "$check_name" "$check_description" "FAIL" "Component not found in GDB directory. Ensure $component_name is correctly installed"
    fi
}

# Function to check if GDB exists
check_gdb_exist() {
    local gdb_version=""
    if command -v gdb-oneapi &> /dev/null; then
        gdb_version=$(gdb-oneapi --version | head -n 1 | awk '{print $NF}')
    fi
    check_file_in_gdb_dir "bin/gdb-oneapi" "Debugger" "$gdb_version"
}

# Function to check if libipt exists
check_libipt_exist() {
    local libipt_version=""
    gdb_base_dir=$(get_gdb_base_dir)
    
    if [[ -f "$gdb_base_dir/lib/libipt.so" ]]; then
        real_libipt_file=$(readlink -f "$gdb_base_dir/lib/libipt.so")
        libipt_version=$(basename "$real_libipt_file" | sed 's/^.*\.so\.//')
    fi
    check_file_in_gdb_dir "lib/libipt.so" "libipt" "$libipt_version"
}


# Function to check if libiga exists
check_libiga_exist() {
    local libiga_version=""

    check_file_in_gdb_dir "lib/libiga64.so" "libiga" "$libiga_version"
}

# Function to check Linux kernel version
check_linux_kernel_version() {
    local check_name="Kernel Version"
    local kernel_version=$(uname -r)
    local check_description="Checking Linux kernel version"

    if [[ "$list" == true ]]; then
        printf "%-18s | %-40s\n" "$check_name" "$check_description" | tee -a "$logfile"
        return
    fi

    IFS='.' read -r -a version_parts <<< "$kernel_version"

    if [[ ${version_parts[0]} -lt 4 ]] || ([[ ${version_parts[0]} -eq 4 ]] && [[ ${version_parts[1]} -lt 14 ]]); then
        log_check_result "$check_name" "$check_description" "FAIL" "Kernel version: $kernel_version. Ensure your Linux kernel version is 4.14 or higher"
    else
        log_check_result "$check_name" "$check_description" "PASS" "Version: $kernel_version"
    fi
}

# Function to check i915 debug
check_i915_debug() {
    local check_name="i915 Debug Check"
    local check_description="Checking i915 debug status"

    if [[ "$list" == true ]]; then
        printf "%-18s | %-40s\n" "$check_name" "$check_description" | tee -a "$logfile"
        return
    fi

    sycl_ls_output=$(sycl-ls 2>/dev/null)
    if [[ $? -ne 0 ]]; then
        log_check_result "$check_name" "$check_description" "FAIL" "Failed to run 'sycl-ls' command. Make sure Intel(R) oneAPI Compiler is installed"
        return
    fi

    dev_ids=$(echo "$sycl_ls_output" | grep -E "Intel\(R\).*Data.*Center.*GPU.*Flex|Intel.*Arc.*A.*Graphics|Intel.*Data.*Center.*GPU.*Max.*1100|Intel.*Data.*Center.*GPU.*Max.*1550|Intel.*Data.*Center.*GPU.*Max.*1350")
    if [[ -z "$dev_ids" ]]; then
        log_check_result "$check_name" "$check_description" "FAIL" "No devices found that support debugging of GPU offload code. Ensure your system has compatible GPU devices"
        return
    fi

    prelim_debug_files=$(find /sys/devices | grep prelim_enable_eu_debug)
    if [[ -z "$prelim_debug_files" ]]; then
        log_check_result "$check_name" "$check_description" "FAIL" "Could not find any i915 devices. Ensure i915 driver is installed and prelim_enable_eu_debug is available"
        return
    fi

    prelim_debug_enabled=true
    for file in $prelim_debug_files; do
        if [[ $(cat "$file") == "0" ]]; then
            prelim_debug_enabled=false
            break
        fi
    done

    if ! $prelim_debug_enabled; then
        cmdline_out=$(cat /proc/cmdline)
        if [[ "$cmdline_out" != *"i915.debug_eu=1"* ]]; then
            log_check_result "$check_name" "$check_description" "FAIL" "i915.debug_eu=1 not found in kernel parameters. Add 'i915.debug_eu=1' to kernel parameters in /etc/default/grub, rebuild GRUB and reboot"
            return
        fi
    fi

    log_check_result "$check_name" "$check_description" "PASS"
}

# Function to check level-zero version
check_level_zero_version() {
    local check_name="Level-Zero Version"
    local check_description="Checking level-zero version"
    local current_ver=""

    if [[ "$list" == true ]]; then
        printf "%-18s | %-40s\n" "$check_name" "$check_description" | tee -a "$logfile"
        return
    fi

    sycl_ls_output=$(sycl-ls 2>/dev/null)
    if [[ $? -ne 0 ]]; then
        log_check_result "$check_name" "$check_description" "FAIL" "Failed to run 'sycl-ls' command for checking level-zero version. Ensure Intel(R) oneAPI Toolkit is installed and 'sycl-ls' command is available"
        return
    fi

    while IFS= read -r line; do
        if [[ "$line" == *"ext_oneapi_level_zero"* && "$line" != *"UHD Graphics"* ]]; then
            current_ver=$(echo "$line" | grep -oP "\[([0-9]+\.[0-9]+\.[0-9]+)\]" | tr -d '[]')
            break
        fi
    done <<< "$sycl_ls_output"

    if [[ -z "$current_ver" ]]; then
        log_check_result "$check_name" "$check_description" "FAIL" "No compatible Level-Zero version found. Ensure Intel(R) oneAPI Toolkit is installed and 'sycl-ls' command is available"
        return
    fi

    check_description="$check_description"

    if [[ $(printf '%s\n' "$LEVEL_ZERO_MIN_VERSION" "$current_ver" | sort -V | head -n1) != "$LEVEL_ZERO_MIN_VERSION" ]]; then
        log_check_result "$check_name" "$check_description" "FAIL" "Update your level-zero installation to version >= $LEVEL_ZERO_MIN_VERSION"
        return
    fi

    log_check_result "$check_name" "$check_description" "PASS" "$current_ver, Required: $LEVEL_ZERO_MIN_VERSION"
}

# Function to check GDB environment variables
check_gdb_env_vars() {
    local check_name="GDB Env Variables"
    local check_description="Checking GDB environment variables"

    if [[ "$list" == true ]]; then
        printf "%-18s | %-40s\n" "$check_name" "$check_description" | tee -a "$logfile"
        return
    fi

    if [[ "$ZET_ENABLE_PROGRAM_DEBUGGING" != "1" ]]; then
        log_check_result "$check_name" "$check_description" "FAIL" "ZET_ENABLE_PROGRAM_DEBUGGING=1 not set"
        return
    fi

    log_check_result "$check_name" "$check_description" "PASS"
}

# Function to check gdb-oneapi processes
check_gdb_oneapi_processes() {
    local check_name="gdb-oneapi Check"
    local check_description="Checking gdb-oneapi processes"

    if [[ "$list" == true ]]; then
        printf "%-18s | %-40s\n" "$check_name" "$check_description" | tee -a "$logfile"
        return
    fi

    gdb_oneapi=$(ps aux | grep gdb-oneapi | grep -v grep)
    if [[ -z "$gdb_oneapi" ]]; then
        log_check_result "$check_name" "$check_description" "PASS"
    else
        log_check_result "$check_name" "$check_description" "WARNING" "Some gdb-oneapi processes are left running. Run 'killall gdb-oneapi' to terminate any gdb-oneapi processes"
    fi
}

# Function to check gdbserver-ze processes
check_gdbserver_ze_processes() {
    local check_name="gdbserver-ze Check"
    local check_description="Checking gdbserver-ze processes"

    if [[ "$list" == true ]]; then
        printf "%-18s | %-40s\n" "$check_name" "$check_description" | tee -a "$logfile"
        return
    fi

    gdbserver_ze=$(ps aux | grep gdbserver-ze | grep -v grep)
    if [[ -z "$gdbserver_ze" ]]; then
        log_check_result "$check_name" "$check_description" "PASS"
    else
        log_check_result "$check_name" "$check_description" "FAIL" "Some gdbserver-ze processes are left running. Kill them with command 'killall gdbserver-ze' and try again"
    fi
}

# Function to check compiler
check_compiler() {
    local check_name="Compiler Check"
    local compiler_version=""
    local check_description="Checking Intel(R) oneAPI Compiler"

    if [[ "$list" == true ]]; then
        printf "%-18s | %-40s\n" "$check_name" "$check_description" | tee -a "$logfile"
        return
    fi

    if command -v icx &> /dev/null; then
        compiler_version=$(icx --version | head -n 1 | awk '{print $NF}')
        check_description="${check_description}"
    else
        log_check_result "$check_name" "$check_description" "FAIL" "Intel(R) oneAPI Compiler is not installed. Install Intel(R) oneAPI Compiler and ensure 'icx' command is available in PATH"
        return
    fi

    log_check_result "$check_name" "$check_description" "PASS" "Version: ${compiler_version}"
}

# Function to check multi GPU support
check_multi_gpu_supported() {
    local check_name="Multi-GPU Support"
    local check_description="Checking multi-GPU support"

    if [[ "$list" == true ]]; then
        printf "%-18s | %-40s\n" "$check_name" "$check_description" | tee -a "$logfile"
        return
    fi

    sycl_ls_output=$(sycl-ls 2>/dev/null)
    if [[ $? -ne 0 ]]; then
        log_check_result "$check_name" "$check_description" "FAIL" "Failed to run 'sycl-ls' command for checking multi-device machine. Ensure Intel(R) oneAPI Toolkit is installed and 'sycl-ls' command is available"
        return
    fi

    dev_ids=$(echo "$sycl_ls_output" | grep "Intel(R).*Data.*Center.*GPU.*Flex")
    if [[ $(echo "$dev_ids" | wc -l) -gt 1 ]]; then
        if [[ -z "$ZE_AFFINITY_MASK" ]]; then
            log_check_result "$check_name" "$check_description" "WARNING" "Multiple devices detected. Use the ZE_AFFINITY_MASK environment variable to filter a device"
        else
            log_check_result "$check_name" "$check_description" "PASS" "Multiple devices detected, but ZE_AFFINITY_MASK is set to filter devices"
        fi
        return
    fi

    log_check_result "$check_name" "$check_description" "PASS"
}

# Main function to run all checks and log results
run_debugger_check() {
    echo "Running debugger environment checks on $machine_name with script version $SCRIPT_VERSION at $(date +'%Y-%m-%d %H:%M:%S')" | tee "$logfile"
    if [[ $(get_OS) == "Linux" ]]; then
        check_multi_gpu_supported
        check_level_zero_version
        check_linux_kernel_version
        check_gdb_exist
        check_libipt_exist
        check_libiga_exist
        check_compiler
        check_i915_debug
        check_gdb_env_vars
        check_gdb_oneapi_processes
        check_gdbserver_ze_processes
    else
        log_message "This check works on Linux only"
        exit 1
    fi

    log_message "Debugger environment checks completed"
}

run_debugger_check

# Print summary
if [[ "$list" != true ]]; then
    echo "Summary of checks:" | tee -a "$logfile"
    echo "Total checks: $total_checks" | tee -a "$logfile"
    echo "Passed checks: $passed_checks" | tee -a "$logfile"
    echo "Failed checks: $failed_checks" | tee -a "$logfile"
    echo "Warning checks: $warning_checks" | tee -a "$logfile"

    if ! grep -q "FAIL" "$logfile" && ! grep -q "WARNING" "$logfile"; then
        echo "All checks passed successfully" | tee -a "$logfile"
    fi

    echo "Text report: ${logfile}" | tee -a "$logfile"
    echo "" | tee -a "$logfile"
fi


## 344cb3c4e
