#!/usr/bin/env python

"""
Copyright 2015-2019 Cray Inc.  All Rights Reserved
Utility to list granted and waiting ldlm locks.
"""

from pykdump.API import *
import argparse
import os

import lustrelib as ll
from crashlib.input import toint

from traceback import print_exc

description = "Dumps lists of granted and waiting ldlm locks for each namespace."

''' Lock Types '''
enum_LDLM_PLAIN = 10
enum_LDLM_EXTENT = 11
enum_LDLM_FLOCK = 12
enum_LDLM_IBITS = 13

LOCKMODES = {
    0:"--",
    1:"EX",
    2:"PW",
    4:"PR",
    8:"CW",
    16:"CR",
    32:"NL",
    64:"GROUP"
}

def lockmode2str(mode):
    return LOCKMODES.get(mode, "??")

def ldlm_dump_lock(lock, pos, lstname):
    obd = None
    imp = None
    if lock == None:
        print("   NULL LDLM lock")
        return
    try:
        if lock.l_handle.hasField("h_ref"):
            refcounter = lock.l_handle.h_ref.refs
        else:
            refcounter = lock.l_refc.counter

        print("   -- Lock: (ldlm_lock) %#x/%#x (rc: %d) (pos: %d/%s) (pid: %d)" % \
              (Addr(lock), lock.l_handle.h_cookie, refcounter,
              pos, lstname, lock.l_pid))
        if lock.l_conn_export:
            obd = lock.l_conn_export.exp_obd
        if lock.l_export and lock.l_export.exp_connection:
            print("       Node: NID %s (remote: %#x) export" % \
                  (ll.nid2str(lock.l_export.exp_connection.c_peer.nid),
                  lock.l_remote_handle.cookie))
        elif obd == None:
            print("       Node: local")
        else:
            imp = obd.u.cli.cl_import
            print("       Node: NID %s (remote: %#x) import " % \
                  (ll.nid2str(imp.imp_connection.c_peer.nid),
                  lock.l_remote_handle.cookie))

        res = lock.l_resource
        print("       Resource: %#x [0x%x:0x%x:0x%x].%x" % \
              (Addr(res),
              res.lr_name.name[0],
              res.lr_name.name[1],
              res.lr_name.name[2],
              res.lr_name.name[3]))

        print("       Req mode: %s, grant mode: %s, rc: %d, read: %d, \
              write: %d flags: %#x" % (lockmode2str(lock.l_req_mode),
              lockmode2str(lock.l_granted_mode),
              refcounter, lock.l_readers, lock.l_writers,
              lock.l_flags))

        lr_type = lock.l_resource.lr_type
        if lr_type == enum_LDLM_EXTENT:
            print("       Extent: %d -> %d (req %d-%d)" % \
                  (lock.l_policy_data.l_extent.start,
                  lock.l_policy_data.l_extent.end,
                  lock.l_req_extent.start, lock.l_req_extent.end))
        elif lr_type == enum_LDLM_FLOCK:
            print("       Pid: %d Flock: 0x%x -> 0x%x" % \
                  (lock.l_policy_data.l_flock.pid,
                  lock.l_policy_data.l_flock.start,
                  lock.l_policy_data.l_flock.end))
        elif lr_type == enum_LDLM_IBITS:
            print("       Bits: %#x" % \
                  (lock.l_policy_data.l_inodebits.bits))
    except (crash.error, IndexError):
        print("   Corrupted lock %#x" % (Addr(lock)))
        return

def ldlm_dump_resource(res):
    res_lr_granted = readSU('struct list_head', Addr(res.lr_granted))
    res_lr_waiting = readSU('struct list_head', Addr(res.lr_waiting))
    try:
        print("-- Resource: (ldlm_resource) %#x [0x%x:0x%x:0x%x].%x (rc: %d)" % \
              (Addr(res), res.lr_name.name[0], res.lr_name.name[1],
               res.lr_name.name[2], res.lr_name.name[3], res.lr_refcount.counter))
    except (crash.error, IndexError):
        print("-- Corrupted resource %#x" % (Addr(res)))
        return
    if not ll.list_empty(res_lr_granted):
        pos = 0
        print("   Granted locks: ")
        tmp = res_lr_granted.next
        while(tmp != res_lr_granted):
            pos += 1
            lock = readSU('struct ldlm_lock',
                          Addr(tmp)-member_offset('struct ldlm_lock', 'l_res_link'))
            ldlm_dump_lock(lock, pos, "grnt")
            try:
                tmp = tmp.next
            except (crash.error, IndexError):
                break
    if not ll.list_empty(res_lr_waiting):
        pos = 0
        print("   Waiting locks: ")
        tmp = res_lr_waiting.next
        while(tmp != res_lr_waiting):
            pos += 1
            lock = readSU('struct ldlm_lock',
                          Addr(tmp)-member_offset('struct ldlm_lock', 'l_res_link'))
            ldlm_dump_lock(lock, pos, "wait")
            try:
                tmp = tmp.next
            except (crash.error, IndexError):
                break

def print_namespace(ns, client_server):
    print("Namespace: (ldlm_namespace) %#x, %s\t(rc: %d, side: %s)\tpoolcnt: %d unused: %d" % \
          (Addr(ns), ll.obd2str(ns.ns_obd), ns.ns_bref.counter,
          client_server, ns.ns_pool.pl_granted.counter, ns.ns_nr_unused))

def ldlm_dump_ns_resources(ns):
    if args.nflag:
        return
    for hnode in ll.cfs_hash_get_nodes(ns.ns_rs_hash):
        offset = member_offset('struct ldlm_resource', 'lr_hash')
        res = readSU('struct ldlm_resource', Addr(hnode) - offset)
        ldlm_dump_resource(res)

def ldlm_dump_all_namespaces(ns_name, client_server):
    ns_list = readSymbol(ns_name)
    for ns in readSUListFromHead(ns_list, 'ns_list_chain', 'struct ldlm_namespace'):
        print_namespace(ns, client_server)
        ldlm_dump_ns_resources(ns)

def ldlm_dumplocks():
    if args.ns_addr:
        ns = readSU('struct ldlm_namespace', args.ns_addr)
        print_namespace(ns, "")
        ldlm_dump_ns_resources(ns)
    else:
        ldlm_dump_all_namespaces('ldlm_srv_namespace_list', "server")
        ldlm_dump_all_namespaces('ldlm_cli_active_namespace_list', "client")
        ldlm_dump_all_namespaces('ldlm_cli_inactive_namespace_list', "inactive")

if __name__ == "__main__":
    description = "Dumps lists of granted and waiting locks for each namespace. " + \
                  "Requires Lustre .ko files to be loaded (see mod command)."
    parser = argparse.ArgumentParser(description=description)
    parser.add_argument("-n", dest="nflag", action='store_true',
        help="Print only namespace information")
    parser.add_argument("ns_addr", nargs="?", default=[], type=toint,
        help="Print only locks under namespace at given address")
    args = parser.parse_args()

    ldlm_dumplocks()
