#!/usr/bin/stap --ldd -d /usr/sbin/libvirtd -c libvirtd
#
# Usage with installed libvirt daemon:
#     stap --ldd -d /usr/sbin/libvirtd -c libvirtd \
#          lock-debug.stp /usr/lib/libvirt.so
#
# If made executable; simple './lock-debug.stp' should work too.
#
# TODOs:
#
# Document usage with uninstalled daemon and libs. Assuming CWD is toplevel
# source git directory, it should be only slight modification to the following:
#
#     ./run stap --ldd -c src/libvirtd -d src/libvirtd
#       examples/systemtap/lock-debug.stp src/libvirt.so
#
# Debug RWLock mechanisms as well.
#


global mx_tolock
global mx_locked


function filter()
{
    if (pid() != target())
        return 1

    return 0
}

probe library = process( %( $# > 0 %? @1 %: "/usr/lib/libvirt.so" %) )
{
    if (filter()) next
}

probe lock = library.function("virMutexLock")
{
    lockname = usymdata($m)
}

probe unlock = library.function("virMutexUnlock")
{
    lockname = usymdata($m)
}

probe begin
{
    %( $# > 1 %? println("error: Too many parameters"); exit();
       %: print("Started, press ^C when the process hangs\n"); %)
}

probe lock.call
{
    mx_tolock[lockname, tid()] = sprint_usyms(ubacktrace())
}

probe lock.return
{
    if ([lockname, tid()] in mx_tolock) {
        mx_locked[lockname, tid()] = mx_tolock[lockname, tid()]
        delete mx_tolock[lockname, tid()]
    } else {
        printf("internal error: lock acquired unwillingly?\n")
    }
}

probe unlock.call
{
    found = 0

    foreach ([lock, tid] in mx_locked) {
        if (lock != lockname)
            continue
        if (tid != tid()) {
            printf("Warning: lock released on different thread that locked it.\n")
            printf("Lock trace:\n%s\n", mx_locked[lock, tid])
            printf("Unlock trace:\n%s\n", sprint_usyms(ubacktrace()))
        }

        found = tid
        break
    }

    if (found && [lockname, found] in mx_locked)
        delete mx_locked[lockname, found]
}

probe end
{
    tmp = 0

    printf("\n=============\n")

    foreach (bt1 = [lock1, tid1] in mx_tolock) {
        deadlock = 0

        foreach (bt2 = [lock2, tid2] in mx_tolock) {
            if (lock1 == lock2) {
                if (!tmp++)
                    printf("The following locks cannot be acquired:\n")

                if (!deadlock++)
                    printf("Lock %s was locked in thread %d with this trace:\n%s\n",
                           lock1, tid1, bt1)

                printf("and is waiting to be locked by thread %d here:\n%s\n",
                       tid2, bt2)
            }
        }
        if (deadlock)
            printf("---\n")
    }
    if (!tmp)
        printf("No deadlocks found, sorry.\n")
}
