#!/usr/bin/env python
# vim: ft=python:ts=4:sw=4:et:ai:cin

# NVML driver for the gpu.nvidia component
import pynvml
from contextlib import contextmanager
import subprocess
import os.path as osp


@contextmanager
def nvml(flags=0):
    pynvml.nvmlInitWithFlags(flags)
    yield
    pynvml.nvmlShutdown()


def process_name(pid, full=False):
    try:
        proc = subprocess.run(
            ["ps", "-o", "cmd=", str(pid)],
            capture_output=True,
            encoding="utf-8",
            shell=False,
        )
        if proc.returncode > 0:
            raise SystemError()
        cmdline = proc.stdout
        return (
            cmdline if full else osp.basename(cmdline.split(" ", maxsplit=1)[0])
        ).strip()
    except:
        return "[unknown]"


with nvml():

    def query(f, *args, default=None, **kw):
        try:
            f = getattr(pynvml, f)
            val = f(*args, **kw)
        except:
            val = default
        if isinstance(val, bytes):
            val = val.decode("utf-8")
        return val

    def query_and_print(name, f, *args, default=None, **kw):
        val = query(f, *args, default=default, **kw)
        print_entry(name, val)
        return val

    def print_entry(k, v):
        print(f"{v!r}," if k is None else f"{k}={v!r},")

    gpu_count = query("nvmlDeviceGetCount")

    print("{")
    for gi in range(gpu_count):
        print("{")
        handle = query("nvmlDeviceGetHandleByIndex", gi)

        def q(f, *args, **kw):
            return query(f, handle, *args, **kw)

        def qp(name, f, *args, **kw):
            return query_and_print(name, f, handle, *args, **kw)

        qp("model_name", "nvmlDeviceGetName")

        memory_info = q("nvmlDeviceGetMemoryInfo")
        print_entry("mem_used", memory_info.used)
        print_entry("mem_total", memory_info.total)
        print_entry("mem_free", memory_info.free)

        util_rates = q("nvmlDeviceGetUtilizationRates")
        print_entry("gpu_util", util_rates.gpu)
        print_entry("mem_util", util_rates.memory)

        qp("fan_speed", "nvmlDeviceGetFanSpeed")
        qp("gpu_temp", "nvmlDeviceGetTemperature", pynvml.NVML_TEMPERATURE_GPU)
        qp(
            "gpu_temp_thres",
            "nvmlDeviceGetTemperatureThreshold",
            pynvml.NVML_TEMPERATURE_THRESHOLD_GPU_MAX,
            default=100,
        )
        print_entry("power_usage", q("nvmlDeviceGetPowerUsage") / 1000.0)
        print_entry("power_limit", q("nvmlDeviceGetPowerManagementLimit") / 1000.0)

        processes = {}
        for proc_type, proc_query in (
            ("c", "nvmlDeviceGetComputeRunningProcesses"),
            ("g", "nvmlDeviceGetGraphicsRunningProcesses"),
        ):
            for proc in q(proc_query):
                pid = proc.pid

                processes[pid] = dict(
                    pid=pid,
                    name=process_name(pid),
                    type=proc_type,
                    gpu_util=0,
                    mem_util=0,
                    gpu_mem=proc.usedGpuMemory,
                    gpu_instance=proc.gpuInstanceId,
                )

        if len(processes) > 0:
            timestamp = 0  # list all processes
            samples = q("nvmlDeviceGetProcessUtilization", timestamp)
            for s in samples:
                if s.pid in processes:
                    processes[s.pid].update(
                        dict(
                            gpu_util=s.smUtil,
                            mem_util=s.memUtil,
                        )
                    )

        processes = list(processes.values())
        processes.sort(key=lambda x: x["gpu_util"], reverse=True)
        print("processes={")
        for p in processes:
            print("{")
            for k in (
                "pid",
                "name",
                "type",
                "gpu_util",
                "mem_util",
                "gpu_mem",
                "gpu_instance",
            ):
                print_entry(k, p[k])
            print("},")
        print("}")
        print("},")
    print("}")
