#!/usr/bin/env python3
"""ROCm_SMI_LIB CLI Tool - AMD-SMI Backend

This tool acts as a command line interface for manipulating
and monitoring the amdgpu kernel, using the AMD-SMI library
instead of the rocm_smi_lib API.
This tool uses the amdsmi Python package to call the AMD SMI library.
Recommended: At least one AMD GPU with ROCm driver installed
Required: AMD SMI library installed (pip install amdsmi)
"""

from __future__ import print_function
import sys
import os
import logging

# Version information
SMI_MAJ = 4
SMI_MIN = 0
SMI_PAT = 0
SMI_HASH = "amdsmi"
__version__ = "%s.%s.%s+%s" % (SMI_MAJ, SMI_MIN, SMI_PAT, SMI_HASH)

# Set to 1 if an error occurs
RETCODE = 0

# If we want JSON format output instead
PRINT_JSON = False
PRINT_CSV = False

# Output formatting
headerString = " ROCm System Management Interface "
footerString = " End of ROCm SMI Log "
appWidth = 106

# DRM ioctl calculation helpers
# Reference: include/uapi/asm-generic/ioctl.h from Linux kernel
# https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/asm-generic/ioctl.h
_IOC_NRBITS = 8  # Number of bits for command number
_IOC_TYPEBITS = 8  # Number of bits for device type
_IOC_SIZEBITS = 14  # Number of bits for data size
_IOC_DIRBITS = 2  # Number of bits for direction

_IOC_NRSHIFT = 0
_IOC_TYPESHIFT = _IOC_NRSHIFT + _IOC_NRBITS
_IOC_SIZESHIFT = _IOC_TYPESHIFT + _IOC_TYPEBITS
_IOC_DIRSHIFT = _IOC_SIZESHIFT + _IOC_SIZEBITS

_IOC_WRITE = 1  # ioctl direction: write (userland -> kernel)


def _IOC(dir, type, nr, size):
    """Create ioctl command number
    Reference: _IOC() macro from include/uapi/asm-generic/ioctl.h
    """
    return (
        (dir << _IOC_DIRSHIFT)
        | (type << _IOC_TYPESHIFT)
        | (nr << _IOC_NRSHIFT)
        | (size << _IOC_SIZESHIFT)
    )


def _IOW(type, nr, size):
    """Create write ioctl command number
    Reference: _IOW() macro from include/uapi/asm-generic/ioctl.h
    """
    return _IOC(_IOC_WRITE, type, nr, size)


# DRM and AMDGPU ioctl constants
# Reference: include/uapi/drm/drm.h and include/uapi/drm/amdgpu_drm.h from Linux kernel
# https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/drm/drm.h
# https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/drm/amdgpu_drm.h
DRM_IOCTL_BASE = ord("d")  # 0x64 - DRM ioctl type character (from drm.h)
DRM_COMMAND_BASE = 0x40  # Base for driver-specific commands (from drm.h)
DRM_AMDGPU_INFO = 0x05  # AMDGPU info command number (from amdgpu_drm.h, line 51)
# Size of struct drm_amdgpu_info - we use minimal size for the ioctl call
# Reference: struct drm_amdgpu_info in include/uapi/drm/amdgpu_drm.h
DRM_AMDGPU_INFO_SIZE = 32

# Calculate DRM_IOCTL_AMDGPU_INFO
# Reference: Line 68 in amdgpu_drm.h:
# #define DRM_IOCTL_AMDGPU_INFO DRM_IOW(DRM_COMMAND_BASE + DRM_AMDGPU_INFO, struct drm_amdgpu_info)
DRM_IOCTL_AMDGPU_INFO = _IOW(
    DRM_IOCTL_BASE, DRM_COMMAND_BASE + DRM_AMDGPU_INFO, DRM_AMDGPU_INFO_SIZE
)

# Setup logging
logging.basicConfig(level=logging.WARNING, format="%(levelname)s: %(message)s")


def driverInitialized():
    """Returns true if amdgpu is found in the list of initialized modules"""
    driverInitialized = False
    if os.path.exists("/sys/module/amdgpu"):
        if os.path.exists("/sys/module/amdgpu/initstate"):
            # amdgpu is loadable module
            with open("/sys/module/amdgpu/initstate") as initstate:
                if "live" in initstate.read():
                    driverInitialized = True
        else:
            # amdgpu is built into the kernel
            driverInitialized = True
    return driverInitialized


def find_kfd_topology_path():
    """Find KFD topology path dynamically"""
    possible_paths = [
        "/sys/class/kfd/kfd/topology/nodes",
        "/sys/devices/virtual/kfd/kfd/topology/nodes",
    ]
    for path in possible_paths:
        if os.path.exists(path):
            return path
    return None


def find_drm_path():
    """Find DRM class path dynamically"""
    possible_paths = ["/sys/class/drm", "/sys/devices/virtual/drm"]
    for path in possible_paths:
        if os.path.exists(path):
            return path
    return None


def initializeRsmi():
    """Initialize AMD SMI library - equivalent to rocm_smi.initializeRsmi()"""
    try:
        import amdsmi

        amdsmi.amdsmi_init()
        return True
    except Exception as e:
        logging.error(f"Failed to initialize AMD SMI: {e}")
        return False


def listDevices():
    """Get list of GPU devices - equivalent to rocm_smi.listDevices()"""
    try:
        import amdsmi

        processors = amdsmi.amdsmi_get_processor_handles()
        return processors
    except Exception as e:
        logging.error(f"Failed to get processor handles: {e}")
        return []


def checkAmdGpus(deviceList):
    """Check if AMD GPUs are present - equivalent to rocm_smi.checkAmdGpus()"""
    if not deviceList:
        logging.warning("No AMD GPUs specified")
        return False
    return True


def check_runtime_pm_status(card_path):
    """
    Check if a specific GPU card is in runtime PM suspended state.
    Equivalent to rocm_smi.check_runtime_pm_status()

    Args:
        card_path: Path to the card (e.g., /sys/class/drm/card0)

    Returns:
        bool: True if suspended, False if active or runtime PM disabled
    """
    try:
        runtime_status = os.path.join(card_path, "device/power/runtime_status")
        runtime_enabled = os.path.join(card_path, "device/power/runtime_enabled")

        # Check if runtime PM is enabled
        if os.path.exists(runtime_enabled):
            with open(runtime_enabled) as f:
                enabled = f.read().strip()
                if "disabled" in enabled or "forbidden" in enabled:
                    return False

        # Check runtime status
        if os.path.exists(runtime_status):
            with open(runtime_status) as f:
                status = f.read().strip()
                return "suspended" in status

        return False
    except:
        return False


def wake_device(card_path):
    """
    Wake a GPU device from runtime PM suspended state.
    Equivalent to rocm_smi.wake_device()

    Uses DRM_IOCTL_AMDGPU_INFO ioctl to wake the device from runtime suspend.

    Args:
        card_path: Path to the card (e.g., /sys/class/drm/card0)

    Returns:
        bool: True if device was woken or already active, False on error
    """
    try:
        import re
        import fcntl
        import struct
        import errno

        # Extract card number from card_path (e.g., card0 -> 0)
        card_name = os.path.basename(card_path)
        card_match = re.match(r"card(\d+)", card_name)
        if not card_match:
            return False

        # Find renderD device in the same DRM directory
        drm_path = os.path.dirname(card_path)
        render_name = None

        # Look for renderD* in the drm directory
        for entry in os.listdir(drm_path):
            if re.match(r"renderD\d+", entry):
                # Check if this renderD is associated with our card
                # by verifying they point to the same device
                entry_link = os.path.join(drm_path, entry)
                card_link = card_path

                try:
                    entry_real = os.path.realpath(entry_link)
                    card_real = os.path.realpath(card_link)

                    # They should share the same parent device directory
                    if os.path.dirname(entry_real) == os.path.dirname(card_real):
                        render_name = entry
                        break
                except:
                    continue

        if not render_name:
            return False

        render_path = f"/dev/dri/{render_name}"

        # Open the DRM device and send ioctl to wake it
        if os.path.exists(render_path):
            try:
                fd = os.open(render_path, os.O_RDWR | os.O_CLOEXEC)

                # Create empty drm_amdgpu_info struct (all zeros)
                request = struct.pack("I" * 8, *([0] * 8))  # 32 bytes of zeros

                # Send DRM_IOCTL_AMDGPU_INFO ioctl to wake the device
                try:
                    fcntl.ioctl(fd, DRM_IOCTL_AMDGPU_INFO, request)
                except OSError as e:
                    # EINVAL is acceptable - device may already be awake
                    if e.errno != errno.EINVAL:
                        os.close(fd)
                        return False

                os.close(fd)
                return True
            except OSError:
                return False

        return False
    except:
        return False


def check_runtime_status():
    """
    Check if any GPUs are in low power state and wake them if needed.
    Equivalent to rocm_smi.check_runtime_status()

    Returns:
        bool: True if all devices are active, False if any are suspended
    """
    try:
        drm_path = find_drm_path()
        if not drm_path:
            return True

        all_active = True
        for card in os.listdir(drm_path):
            if card.startswith("card") and "-" not in card:
                card_path = os.path.join(drm_path, card)

                # Check if this card is suspended
                is_suspended = check_runtime_pm_status(card_path)
                if is_suspended:
                    all_active = False
                    # Try to wake the device
                    wake_device(card_path)

        return all_active
    except:
        return True


def get_bdf(processor):
    """Get BDF (Bus:Device.Function)"""
    try:
        import amdsmi

        bdf = amdsmi.amdsmi_get_gpu_device_bdf(processor)
        return bdf
    except:
        return None


def bdf_to_location_id(bdf):
    """Convert BDF string to location_id integer used by KFD
    BDF format: "0000:44:00.0" -> location_id calculation
    """
    try:
        # Parse BDF: domain:bus:device.function
        parts = bdf.replace(":", " ").replace(".", " ").split()
        if len(parts) >= 4:
            domain = int(parts[0], 16)
            bus = int(parts[1], 16)
            device = int(parts[2], 16)
            function = int(parts[3], 16)

            # KFD location_id = (bus << 8) | (device << 3) | function
            location_id = (bus << 8) | (device << 3) | function
            return location_id
    except:
        pass
    return None


def get_device_info(processor, bdf=None):
    """Get device ID and GPU ID (GUID) from KFD topology"""
    try:
        import amdsmi

        asic_info = amdsmi.amdsmi_get_gpu_asic_info(processor)
        device_id = asic_info.get("device_id", "N/A")

        # Get GPU ID from KFD topology by matching location_id
        kfd_path = find_kfd_topology_path()
        gpu_id = "N/A"

        if kfd_path and os.path.exists(kfd_path) and bdf:
            location_id = bdf_to_location_id(bdf)
            if location_id is not None:
                # Look through KFD nodes to find matching location_id
                for node_dir in os.listdir(kfd_path):
                    props_file = os.path.join(kfd_path, node_dir, "properties")
                    if os.path.exists(props_file):
                        with open(props_file) as f:
                            props_dict = {}
                            for line in f:
                                if " " in line:
                                    parts = line.strip().split(None, 1)
                                    if len(parts) == 2:
                                        props_dict[parts[0]] = parts[1]

                            # Match by location_id
                            if props_dict.get("location_id") == str(location_id):
                                gpu_id_file = os.path.join(kfd_path, node_dir, "gpu_id")
                                if os.path.exists(gpu_id_file):
                                    with open(gpu_id_file) as gf:
                                        gpu_id = gf.read().strip()
                                break

        return {"device_id": device_id, "guid": gpu_id}
    except Exception as e:
        return {"device_id": "N/A", "guid": "N/A"}


def get_kfd_node_id(processor, bdf=None):
    """Get KFD node ID by matching BDF to location_id in topology"""
    try:
        import amdsmi

        if not bdf:
            return amdsmi.amdsmi_topo_get_numa_node_number(processor)

        kfd_path = find_kfd_topology_path()
        if kfd_path and os.path.exists(kfd_path):
            location_id = bdf_to_location_id(bdf)
            if location_id is not None:
                # Look through KFD nodes to find matching location_id
                for node_dir in os.listdir(kfd_path):
                    props_file = os.path.join(kfd_path, node_dir, "properties")
                    if os.path.exists(props_file):
                        with open(props_file) as f:
                            props_dict = {}
                            for line in f:
                                if " " in line:
                                    parts = line.strip().split(None, 1)
                                    if len(parts) == 2:
                                        props_dict[parts[0]] = parts[1]

                            # Match by location_id
                            if props_dict.get("location_id") == str(location_id):
                                return int(node_dir)

        # Fallback to NUMA node
        return amdsmi.amdsmi_topo_get_numa_node_number(processor)
    except:
        return None


def get_temperature(processor, temp_type="edge"):
    """Get GPU temperature"""
    try:
        import amdsmi

        # Map temp_type string to enum
        temp_type_map = {
            "edge": amdsmi.AmdSmiTemperatureType.EDGE,
            "junction": amdsmi.AmdSmiTemperatureType.HOTSPOT,
            "hotspot": amdsmi.AmdSmiTemperatureType.HOTSPOT,
            "memory": amdsmi.AmdSmiTemperatureType.VRAM,
            "vram": amdsmi.AmdSmiTemperatureType.VRAM,
        }

        temp_enum = temp_type_map.get(temp_type.lower(), amdsmi.AmdSmiTemperatureType.EDGE)

        temp = amdsmi.amdsmi_get_temp_metric(
            processor, temp_enum, amdsmi.AmdSmiTemperatureMetric.CURRENT
        )
        return temp
    except:
        # Try other temperature types
        try:
            for ttype in [
                amdsmi.AmdSmiTemperatureType.HOTSPOT,
                amdsmi.AmdSmiTemperatureType.EDGE,
                amdsmi.AmdSmiTemperatureType.VRAM,
            ]:
                try:
                    temp = amdsmi.amdsmi_get_temp_metric(
                        processor, ttype, amdsmi.AmdSmiTemperatureMetric.CURRENT
                    )
                    return temp
                except:
                    continue
        except:
            pass
        return None


def getTemperatureLabel(deviceList):
    """Discover the first available temperature label - matches rocm-smi priority"""
    import amdsmi

    if not deviceList:
        return "edge"

    # ROCm-SMI always prefers edge temperature first, then junction, then memory
    # This matches the original rocm-smi behavior
    processor = deviceList[0]
    temp_types = [
        ("edge", amdsmi.AmdSmiTemperatureType.EDGE),
        ("junction", amdsmi.AmdSmiTemperatureType.HOTSPOT),
        ("memory", amdsmi.AmdSmiTemperatureType.VRAM),
    ]

    for label, temp_type in temp_types:
        try:
            temp = amdsmi.amdsmi_get_temp_metric(
                processor, temp_type, amdsmi.AmdSmiTemperatureMetric.CURRENT
            )
            if temp is not None and temp > 0:
                return label
        except:
            continue

    return "edge"


def getPowerLabel(deviceList):
    """Discover the first available power label - matches rocm-smi output"""
    import amdsmi

    if not deviceList:
        return "(Avg)"

    # ROCm-SMI always uses "(Avg)" for Average Graphics Package Power
    # Return (Avg) to match rocm-smi behavior
    return "(Avg)"


def get_power_from_sysfs(bdf):
    """Get power from sysfs without waking GPU"""
    try:
        if not bdf:
            return None

        drm_path = find_drm_path()
        if not drm_path:
            return None

        for card in os.listdir(drm_path):
            if card.startswith("card") and "-" not in card:
                card_bdf_path = os.path.join(drm_path, card, "device/uevent")
                if os.path.exists(card_bdf_path):
                    with open(card_bdf_path) as f:
                        content = f.read()
                        if bdf in content:
                            hwmon_path = os.path.join(drm_path, card, "device/hwmon")
                            if os.path.exists(hwmon_path):
                                for hwmon in os.listdir(hwmon_path):
                                    power_file = os.path.join(hwmon_path, hwmon, "power1_average")
                                    if os.path.exists(power_file):
                                        with open(power_file) as f:
                                            power_uw = int(f.read().strip())
                                            return power_uw / 1000000.0
                            break
        return None
    except:
        return None


def get_power(processor, bdf=None):
    """Get power consumption"""
    try:
        import amdsmi

        # Try sysfs first (doesn't wake GPU)
        power_sysfs = get_power_from_sysfs(bdf)
        if power_sysfs is not None:
            return power_sysfs

        # Fallback to API
        power_dict = amdsmi.amdsmi_get_power_info(processor)
        if "average_socket_power" in power_dict:
            power_w = power_dict["average_socket_power"]
            if power_w != "N/A" and isinstance(power_w, (int, float)):
                return float(power_w)
        if "socket_power" in power_dict:
            power_w = power_dict["socket_power"]
            if power_w != "N/A" and isinstance(power_w, (int, float)):
                return float(power_w)
        return None
    except:
        return None


def get_clock_freq_from_sysfs(bdf, clk_type):
    """Get clock frequency from sysfs without waking GPU"""
    try:
        import amdsmi

        if not bdf:
            return None

        clk_file_map = {
            amdsmi.AmdSmiClkType.SYS: "pp_dpm_sclk",
            amdsmi.AmdSmiClkType.MEM: "pp_dpm_mclk",
        }

        if clk_type not in clk_file_map:
            return None

        drm_path = find_drm_path()
        if not drm_path:
            return None

        for card in os.listdir(drm_path):
            if card.startswith("card") and "-" not in card:
                card_bdf_path = os.path.join(drm_path, card, "device/uevent")
                if os.path.exists(card_bdf_path):
                    with open(card_bdf_path) as f:
                        content = f.read()
                        if bdf in content:
                            dpm_file = os.path.join(
                                drm_path, card, f"device/{clk_file_map[clk_type]}"
                            )
                            if os.path.exists(dpm_file):
                                with open(dpm_file) as f:
                                    for line in f:
                                        if "*" in line:
                                            parts = line.split(":")
                                            if len(parts) >= 2:
                                                freq_str = (
                                                    parts[1]
                                                    .replace("*", "")
                                                    .strip()
                                                    .replace("Mhz", "")
                                                    .replace("MHz", "")
                                                )
                                                return float(freq_str)
                            break
        return None
    except:
        return None


def get_clock_freq(processor, clk_type, bdf=None):
    """Get clock frequency"""
    try:
        import amdsmi

        # Try sysfs first (doesn't wake GPU)
        freq_sysfs = get_clock_freq_from_sysfs(bdf, clk_type)
        if freq_sysfs is not None:
            return freq_sysfs

        # Fallback to API
        freq_dict = amdsmi.amdsmi_get_clk_freq(processor, clk_type)
        if "current" in freq_dict and "frequency" in freq_dict:
            current_idx = freq_dict["current"]
            frequencies = freq_dict["frequency"]
            if current_idx < len(frequencies):
                freq_hz = frequencies[current_idx]
                return freq_hz / 1000000
        return None
    except:
        return None


def get_gpu_usage_from_sysfs(bdf):
    """Get GPU usage from sysfs without waking GPU"""
    try:
        if not bdf:
            return None

        drm_path = find_drm_path()
        if not drm_path:
            return None

        for card in os.listdir(drm_path):
            if card.startswith("card") and "-" not in card:
                card_bdf_path = os.path.join(drm_path, card, "device/uevent")
                if os.path.exists(card_bdf_path):
                    with open(card_bdf_path) as f:
                        content = f.read()
                        if bdf in content:
                            busy_file = os.path.join(drm_path, card, "device/gpu_busy_percent")
                            if os.path.exists(busy_file):
                                with open(busy_file) as f:
                                    return int(f.read().strip())
                            break
        return None
    except:
        return None


def get_gpu_usage(processor, bdf=None):
    """Get GPU utilization percentage"""
    try:
        import amdsmi

        # Try sysfs first (doesn't wake GPU)
        usage_sysfs = get_gpu_usage_from_sysfs(bdf)
        if usage_sysfs is not None:
            return usage_sysfs

        # Fallback to API
        usage = amdsmi.amdsmi_get_gpu_busy_percent(processor)
        return usage
    except:
        return None


def get_memory_usage(processor):
    """Get VRAM usage percentage"""
    try:
        import amdsmi

        vram_used = amdsmi.amdsmi_get_gpu_memory_usage(processor, amdsmi.AmdSmiMemoryType.VRAM)
        vram_total = amdsmi.amdsmi_get_gpu_memory_total(processor, amdsmi.AmdSmiMemoryType.VRAM)
        if vram_total > 0:
            return (vram_used / vram_total) * 100
        return None
    except:
        return None


def get_fan_speed(processor):
    """Get fan speed percentage"""
    try:
        import amdsmi

        fan_speed = amdsmi.amdsmi_get_gpu_fan_speed(processor, 0)
        return fan_speed
    except:
        # Fan not supported (e.g., OAM modules) - return 0
        return 0


def get_perf_level(processor):
    """Get performance level"""
    try:
        import amdsmi

        perf_level = amdsmi.amdsmi_get_gpu_perf_level(processor)
        if isinstance(perf_level, str):
            if "AUTO" in perf_level:
                return "auto"
            elif "LOW" in perf_level:
                return "low"
            elif "HIGH" in perf_level:
                return "high"
            elif "MANUAL" in perf_level:
                return "manual"
            elif "STABLE_STD" in perf_level:
                return "stable_std"
            elif "STABLE_PEAK" in perf_level:
                return "stable_peak"
            elif "STABLE_MIN_MCLK" in perf_level:
                return "stable_min_mclk"
            elif "STABLE_MIN_SCLK" in perf_level:
                return "stable_min_sclk"
        return perf_level
    except:
        return None


def get_power_cap(processor):
    """Get power cap in Watts"""
    try:
        import amdsmi

        power_cap_info = amdsmi.amdsmi_get_power_cap_info(processor)
        if "power_cap" in power_cap_info:
            return power_cap_info["power_cap"] / 1000000.0
        return None
    except:
        return None


def get_memory_partition(processor):
    """Get memory partition"""
    try:
        import amdsmi

        partition = amdsmi.amdsmi_get_gpu_memory_partition(processor)
        # partition is already a string like 'NPS1', 'NPS2', etc.
        if isinstance(partition, str):
            return partition
        # If it's an enum, convert it
        partition_names = {
            amdsmi.AmdSmiMemoryPartitionType.NPS1: "NPS1",
            amdsmi.AmdSmiMemoryPartitionType.NPS2: "NPS2",
            amdsmi.AmdSmiMemoryPartitionType.NPS4: "NPS4",
            amdsmi.AmdSmiMemoryPartitionType.NPS8: "NPS8",
        }
        return partition_names.get(partition, str(partition))
    except:
        return "N/A"


def get_compute_partition(processor):
    """Get compute partition"""
    try:
        import amdsmi

        partition = amdsmi.amdsmi_get_gpu_compute_partition(processor)
        # partition is already a string like 'SPX', 'DPX', etc.
        if isinstance(partition, str):
            return partition
        # If it's an enum, convert it
        partition_names = {
            amdsmi.AmdSmiComputePartitionType.SPX: "SPX",
            amdsmi.AmdSmiComputePartitionType.DPX: "DPX",
            amdsmi.AmdSmiComputePartitionType.TPX: "TPX",
            amdsmi.AmdSmiComputePartitionType.QPX: "QPX",
            amdsmi.AmdSmiComputePartitionType.CPX: "CPX",
        }
        return partition_names.get(partition, str(partition))
    except:
        return "N/A"


def printLogSpacer(displayString=None, fill="=", contentSizeToFit=0):
    """Prints formatted spacer line

    :param displayString: String to display in the center
    :param fill: Character to use for padding
    :param contentSizeToFit: Width to fit content (overrides appWidth)
    """
    global appWidth, PRINT_JSON
    resizeValue = appWidth
    if contentSizeToFit != 0:
        resizeValue = contentSizeToFit
    if resizeValue % 2:  # if odd -> make even
        resizeValue += 1

    if not PRINT_JSON:
        if displayString:
            if len(displayString) % 2:
                displayString += fill
            logSpacer = (
                fill * int((resizeValue - len(displayString)) / 2)
                + displayString
                + fill * int((resizeValue - len(displayString)) / 2)
            )
        else:
            logSpacer = fill * resizeValue
        print(logSpacer)


def printLog(device, logstr, logname, useItalics=False):
    """Print out to the log

    :param device: Device to print (can be None)
    :param logstr: String to print
    :param logname: Log name (can be None)
    :param useItalics: Whether to use italics (not used in this implementation)
    """
    global PRINT_JSON
    if not PRINT_JSON:
        print(logstr)


def showAllConcise(deviceList):
    """
    Equivalent to rocm_smi.showAllConcise()
    Display critical info for all devices in a concise format
    """
    global PRINT_JSON, headerString, footerString
    import amdsmi

    if PRINT_JSON:
        print("NOT_SUPPORTED: Cannot print JSON/CSV output for concise output")
        sys.exit(1)

    silent = True

    # CRITICAL: Cache BDF first to minimize GPU wake-up
    bdf_cache = {}
    for i, processor in enumerate(deviceList):
        bdf_cache[i] = get_bdf(processor)

    # Detect temperature and power labels
    available_temp_type = getTemperatureLabel(deviceList)
    temp_label = f"({available_temp_type.capitalize()})"
    power_label = getPowerLabel(deviceList)

    # Header and subheader setup
    header = [
        "Device",
        "Node",
        "IDs",
        "",
        "Temp",
        "Power",
        "Partitions",
        "SCLK",
        "MCLK",
        "Fan",
        "Perf",
        "PwrCap",
        "VRAM%",
        "GPU%",
    ]
    subheader = [
        "",
        "",
        "(DID,",
        "GUID)",
        temp_label,
        power_label,
        "(Mem, Compute, ID)",
        "",
        "",
        "",
        "",
        "",
        "",
        "",
    ]

    # Add additional spaces to match header
    for idx, item in enumerate(subheader):
        header_size = len(header[idx])
        subheader_size = len(subheader[idx])
        if header_size != subheader_size:
            numSpacesToFill_subheader = header_size - subheader_size
            numSpacesToFill_header = subheader_size - header_size
            if numSpacesToFill_subheader > 0:
                subheader[idx] = subheader[idx] + (" " * numSpacesToFill_subheader)
            if numSpacesToFill_header > 0:
                header[idx] = header[idx] + (" " * numSpacesToFill_header)

    head_widths = [len(head) + 2 for head in header]
    values = {}
    degree_sign = "\N{DEGREE SIGN}"

    # Collect data for each device
    for i, processor in enumerate(deviceList):
        bdf = bdf_cache.get(i)

        # Read sysfs values FIRST (before other API calls that might wake GPU)
        sclk = get_clock_freq(processor, amdsmi.AmdSmiClkType.SYS, bdf)
        sclk_str = f"{sclk:.0f}Mhz" if sclk is not None else "N/A"

        mclk = get_clock_freq(processor, amdsmi.AmdSmiClkType.MEM, bdf)
        mclk_str = f"{mclk:.0f}Mhz" if mclk is not None else "N/A"

        gpu_usage = get_gpu_usage(processor, bdf)
        gpu_str = f"{gpu_usage}%" if gpu_usage is not None else "N/A"

        power = get_power(processor, bdf)
        power_str = f"{power:.1f}W" if power is not None else "N/A"

        # Now get other info (may wake GPU but critical sysfs values already read)
        node = get_kfd_node_id(processor, bdf)
        if node is None:
            node = i

        device_info = get_device_info(processor, bdf)
        device_id = device_info["device_id"]
        guid_str = str(device_info["guid"])
        if len(guid_str) > 10:
            guid_str = guid_str[-5:]

        temp = get_temperature(processor, available_temp_type)
        temp_val = f"{temp:.1f}{degree_sign}C" if temp is not None else "N/A"

        mem_part = get_memory_partition(processor)
        comp_part = get_compute_partition(processor)
        combined_partition_data = f"{mem_part}, {comp_part}, 0"

        fan = get_fan_speed(processor)
        fan_str = f"{fan}%" if fan is not None else "N/A"

        perf = get_perf_level(processor)
        perf_str = perf if perf is not None else "N/A"

        pwr_cap = get_power_cap(processor)
        pwr_cap_str = f"{pwr_cap:.1f}W" if pwr_cap is not None else "N/A"

        vram = get_memory_usage(processor)
        vram_str = f"{vram:.0f}%" if vram is not None else "N/A"

        # Store values
        values[f"card{i}"] = [
            i,
            node,
            str(device_id) + ", ",
            str(guid_str),
            temp_val,
            power_str,
            combined_partition_data,
            sclk_str,
            mclk_str,
            fan_str,
            perf_str,
            pwr_cap_str,
            vram_str,
            gpu_str,
        ]

    # Calculate column widths
    val_widths = {}
    for i in range(len(deviceList)):
        val_widths[i] = [len(str(val)) + 2 for val in values[f"card{i}"]]

    max_widths = head_widths
    for i in range(len(deviceList)):
        for col in range(len(val_widths[i])):
            max_widths[col] = max(max_widths[col], val_widths[i][col])

    # Display concise info
    header_output = "".join(
        word.ljust(max_widths[col]) for col, word in zip(range(len(max_widths)), header)
    )
    subheader_output = "".join(
        word.ljust(max_widths[col]) for col, word in zip(range(len(max_widths)), subheader)
    )

    printLogSpacer(headerString, contentSizeToFit=len(header_output))
    printLogSpacer(" Concise Info ", contentSizeToFit=len(header_output))
    printLog(None, header_output, None)
    printLog(None, subheader_output, None, useItalics=True)
    printLogSpacer(fill="=", contentSizeToFit=len(header_output))

    for i in range(len(deviceList)):
        printLog(
            None,
            "".join(
                str(word).ljust(max_widths[col])
                for col, word in zip(range(len(max_widths)), values[f"card{i}"])
            ),
            None,
        )

    printLogSpacer(footerString, contentSizeToFit=len(header_output))


def main():
    """
    Main function - equivalent to rocm_smi.py main execution
    """
    global RETCODE

    # Import amdsmi
    try:
        import amdsmi
    except ImportError:
        print("ERROR: Could not import amdsmi module")
        print("Install with: pip install amdsmi")
        return 1

    # Check if driver is initialized
    if not driverInitialized():
        logging.error("Unable to detect amdgpu driver. Exiting...")
        return 1

    # Initialize AMD SMI (equivalent to rocm_smi.initializeRsmi())
    if not initializeRsmi():
        logging.error("Failed to initialize AMD SMI")
        return 1

    try:
        # Get processor handles (equivalent to rocm_smi.listDevices())
        deviceList = listDevices()

        if not deviceList:
            logging.error("No AMD GPU devices found")
            return 1

        # Check AMD GPUs (equivalent to rocm_smi.checkAmdGpus())
        if not checkAmdGpus(deviceList):
            logging.warning("No AMD GPUs specified")

        # Check runtime status (equivalent to rocm_smi.check_runtime_status())
        if not check_runtime_status():
            print(
                "\nWARNING: AMD GPU device(s) is/are in a low-power state. Check power control/runtime_status\n"
            )

        # Call showAllConcise (equivalent to rocm_smi.showAllConcise(deviceList))
        showAllConcise(deviceList)

    finally:
        # Shutdown AMD SMI
        try:
            amdsmi.amdsmi_shut_down()
        except:
            pass

    return RETCODE


if __name__ == "__main__":
    sys.exit(main())
