#!/bin/bash

NUM_MOUNTS=50
FILES_PER_MOUNT=10
MOUNT_BASE=/mnt/centss07
MOUNT_DIRS=""
MGS_NID=centss07
FS_NAME=centss07
FS_OPTS="rw,flock,lazystatfs"

function pad_num() {
    local num=$1
    local req_width=$2
    
    if [[ -z $num ]] || [[ -z $req_width ]]; then
        return 1
    fi

    local num_width=${#num}

    if [[ $num_width -gt $req_width ]]; then
        echo $num
        return 1
    fi

    local pad_width=$((req_width - $num_width))
    local pad=""
    local i
    for i in $(seq 1 $pad_width); do
        pad+="0"
    done
    echo ${pad}${num}
}

function make_mounts() {
    local rc
    MOUNT_DIRS=""
    for i in $(seq 1 $NUM_MOUNTS); do
        local dir=$MOUNT_BASE/$(pad_num $i 2)
        mkdir -p $MOUNT_BASE/$(pad_num $i 2)
        rc=$?
        if [[ rc -ne 0 ]]; then
            return $rc
        fi
        MOUNT_DIRS="${MOUNT_DIRS:+$MOUNT_DIRS }$dir"
    done
    return $rc
}

function do_mounts() {
    for dir in $MOUNT_DIRS; do
        mount -t lustre $MGS_NID:/$FS_NAME $dir ${FS_OPTS:+-o $FS_OPTS}
        rc=$?
        if [[ rc -ne 0 ]]; then
            return $rc
        fi
    done
    return $rc
}

function do_umounts() {
    for dir in $MOUNT_DIRS; do
        umount $dir
    done
}

function remove_mounts() {
    for dir in $MOUNT_DIRS; do
        rmdir $dir
    done
}

function create_files() {
    local dir
    local i
    local rc
    for dir in $MOUNT_DIRS; do
        # Set up a subdirectory which is written to through only this mount
        local write_dir=$dir/mount_$(basename $dir)
        mkdir -p $write_dir
        for i in $(seq 1 $FILES_PER_MOUNT); do
            local file=$write_dir/file_$(pad_num $i 2)
            dd of=$file if=/dev/zero bs=4192 count=1024 &> /dev/null
            rc=$?
            if [[ $rc -ne 0 ]]; then
               return $rc 
            fi
        done
    done
}

function remove_files() {
    local mount_dir_array=( $MOUNT_DIRS )
    rm -rf ${mount_dir_array[0]}/*
    cancel_unused_locks -p
}

# Print the sum of the numbers which are listed one per line on stdin
calc_total() {
    awk 'BEGIN{total=0}; {total+=$1}; END{print total}'
}

# Print the total of the lock_unused_count across all namespaces containing the
# given wildcard. If the namespace wildcard is omitted, all namespaces will be
# matched.
# Usage: total_unused_locks [namespace_wildcard]
total_unused_locks() {
    lctl get_param -n "ldlm.namespaces.*$1*.lock_unused_count" | calc_total
}

# Print the total of the lock_count across all namespaces containing the given
# wildcard. If the namespace wilcard is omitted, all namespaces will be matched.
# Usage: total_used_locks [namespace_wildcard]
total_used_locks() {
     lctl get_param -n "ldlm.namespaces.*$1*.lock_count" | calc_total
}

# Cancel unused locks
# Usage: cancel_unused_locks [-p]
#   -p  cancel unused locks in parallel
function cancel_unused_locks() {
    if [[ -n $1 ]]; then
        if [[ $1 != "-p" ]]; then
            echo "Invalid option passed to cancel_unused_locks."
        fi
    fi

    lctl set_param -n $1 ldlm.namespaces.*.lru_size=clear
}

function run_perf_test() {
    local options="$1"
    local before_unused_locks
    local before_used_locks
    local after_unused_locks
    local after_used_locks

    echo "Making $NUM_MOUNTS mount directories."
    make_mounts
    if [[ $? -ne 0 ]]; then
        echo "Error making mount points."
        return 1;
    fi

    echo "Mounting Lustre filesystem $FS_NAME at each mount point."
    do_mounts
    if [[ $? -ne 0 ]]; then
        echo "Error mounting file system."
        return 1;
    fi

    echo "Creating $FILES_PER_MOUNT files through each mount point."
    create_files
    if [[ $? -ne 0 ]]; then
        echo "Error creating files at each mount point."
        return 1;
    fi

    before_unused_locks=$(total_unused_locks)
    before_used_locks=$(total_used_locks)

    echo "Unused locks before: $before_unused_locks."
    echo "Used locks before: $before_used_locks."

    echo "Clearing unused locks...."

    time cancel_unused_locks $options

    after_unused_locks=$(total_unused_locks)
    after_used_locks=$(total_used_locks)

    echo "Unused locks after: $after_unused_locks."
    echo "Used locks after: $after_used_locks."

    echo "Cleaning up."
    remove_files
    do_umounts
    remove_mounts
}

print_multiple() {
    str=$1
    num=$2
    v=$(printf "%-${num}s" "$str")
    echo "${v// /$str}"
}

function print_test_sep() {
    local msg="$1"
    local msg_len=${#msg}

    echo
    print_multiple "=" $msg_len
    echo $msg
    print_multiple "=" $msg_len
    echo
}

function print_and_exec() {
    local cmd="$@"
    echo "Executing '$cmd'"
    time $cmd
}

### Performance comparison between serial and parallel

print_test_sep "Running serial performance test."
run_perf_test

print_test_sep "Running parallel performance test."
run_perf_test -p

### Functionality tests. 
NUM_MOUNTS=5
FILES_PER_MOUNT=10

make_mounts
do_mounts

print_test_sep "Testing with multiple wildcards."
create_files
print_and_exec "lctl set_param -p ldlm.namespaces.*mdc*.lru_size=clear ldlm.namespaces.*osc*.lru_size=clear"
if [[ $(total_unused_locks) -ne 0 ]]; then
    echo "Error: failed to clear unused locks."
fi
remove_files

print_test_sep "Testing with brace wildcards."
create_files
print_and_exec "lctl set_param -p ldlm.namespaces.*{osc,mdc}*.lru_size=clear"
if [[ $(total_unused_locks) -ne 0 ]]; then
    echo "Error: failed to clear unused locks."
fi
remove_files

print_test_sep "Testing with overlapping wildcards."
create_files
print_and_exec "lctl set_param -p ldlm.namespaces.*.lru_size=clear ldlm.namespaces.*osc*.lru_size=clear"
if [[ $(total_unused_locks) -ne 0 ]]; then
    echo "Error: failed to clear unused locks."
fi
remove_files

print_test_sep "Testing with incorrect trailing argument."
create_files
print_and_exec "lctl set_param -p ldlm.namespaces.*.lru_size=clear blah"
if [[ $(total_unused_locks) -ne 0 ]]; then
    echo "Error: failed to clear unused locks."
fi
remove_files

print_test_sep "Testing with incorrect leading argument."
create_files
print_and_exec "lctl set_param -p blah ldlm.namespaces.*.lru_size=clear"
if [[ $(total_unused_locks) -eq 0 ]]; then
    echo "Error: set_param should have failed to clear locks due to invalid arguments."
fi
remove_files


do_umounts
remove_mounts
