#!/bin/sh

#*************************************************
#Copyright (C), 2020-2024, KylinSoft. Co., Ltd. 
#File name: HealthMEM
#Author: LiuPei
#Version: 1
#Description: Measures MEM idling and writes #health-mem status into the CIB
#Date: 2022.9.27
#Function List: 
#1.healthmem_monitor(): Monitor memory usage and reports memory status
#2.healthmem_stop(): stop the monitoring of mem
#3.healthmem_start(): start the monitoring of mem
#4.UpdateStat(): Modify the memory state
#5.SysInfo_hdd_units(): Memory unit conversion
#6.SysInfo_mem_units(): Internal access integer
#7.SysInfo_megabytes(): Memory unit conversion
#History: 
#1. Date: 2022.9.27 Author: LiuPei Initialization script
#2. Date: 2022.10.9 Author: LiuPei Add fix_HealthMEM.patch
#3. Date: 2022.11.2 Author: LiuPei Add email alarm function
#4. Date: 2022.11.14 Author: LiuPei Optimized intelligent migration
#5. Date: 2022.11.23 Author: LiuPei fix bug 149417 149420
#6. Date: 2022.12.2 Author: LiuPei fix  determining the remaining value of MEM memory
#7. Date: 2023.6.7 Author: LiuPei fix the computing of mem and the monitor of cpu,fix the failure of sending email
#8. Date: 2023.8.10 Author: LiuPei fix the computing of mem
#9. Date: 2023.9.5 Author: LiuPei remove mem unit
#10.Date: 2023.12.7 Author: LiuPei Optimize intelligent migration for duplicate email sending logic
#11.Date: 2023.12.28 Author: LiuPei optimize the intelligent migration function
#*************************************************

#######################################################################
# Initialization:

: ${OCF_FUNCTIONS:="${OCF_ROOT}/resource.d/heartbeat/.ocf-shellfuncs"}
. "${OCF_FUNCTIONS}"
: ${__OCF_ACTION:="$1"}
: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat}
. ${OCF_FUNCTIONS_DIR}/health.sh

#######################################################################

meta_data() {
    cat <<END
<?xml version="1.0"?>
<resource-agent name="HealthMEM" version="@VERSION@">
<version>1.1</version>

<longdesc lang="en">
System health agent that measures the MEM idling and updates the #health-mem attribute.
</longdesc>
<shortdesc lang="en">System health MEM usage</shortdesc>

<parameters>
<parameter name="state" unique-group="state">
<longdesc lang="en">
Location to store the resource state in.
</longdesc>
<shortdesc lang="en">State file</shortdesc>
<content type="string" default="${HA_VARRUN%%/}/health-mem-${OCF_RESOURCE_INSTANCE}.state" />
</parameter>

<parameter name="yellow_mem" reloadable="1">
<longdesc lang="en">
Lower (!) limit of idle to switch the health attribute to yellow. I.e.
the #health-mem will go yellow if the idle of the MEM falls below 50.
</longdesc>
<shortdesc lang="en">Lower limit for yellow health attribute</shortdesc>
<content type="string" default="50"/>
</parameter>

<parameter name="red_mem" reloadable="1">
<longdesc lang="en">
Lower (!) limit of idle to switch the health attribute to red. I.e.
the #health-mem will go red if the idle of the MEM falls below 10.
</longdesc>
<shortdesc lang="en">Lower limit for red health attribute</shortdesc>
<content type="string" default="10"/>
</parameter>

<parameter name="dampening" reloadable="1">
<longdesc lang="en">
The time to wait (dampening) in seconds for further changes before writing
</longdesc>
<shortdesc lang="en">The time to wait (dampening) in seconds for further changes
before writing</shortdesc>
<content type="string" default="0s"/>
</parameter>

</parameters>

<actions>
<action name="start"        timeout="10s" />
<action name="stop"         timeout="30s" />
<action name="monitor"      timeout="30s" interval="20s" start-delay="0s" />
<action name="reload-agent" timeout="20s" />
<action name="meta-data"    timeout="5s" />
<action name="validate-all"   timeout="10s" depth="0" />
</actions>
</resource-agent>
END
}

#######################################################################
healthmem_usage() {
    cat <<END
usage: $0 {start|stop|monitor|reload-agent|validate-all|meta-data}

Expects to have a fully populated OCF RA-compliant environment set.
END
}

UpdateStat() {
    name="$1"; shift
    value="$*"
    printf "%s:\t%s\n" "$name" "$value"
    if [ "$__OCF_ACTION" = "start" ] ; then
        "${HA_SBIN_DIR}/attrd_updater" ${OCF_RESKEY_delay} -S status -n $name -B "$value"
    else
        "${HA_SBIN_DIR}/attrd_updater" ${OCF_RESKEY_delay} -S status -n $name -v "$value"
    fi
}

SysInfo_hdd_units() {
    # Defauts to size in gigabytes

    case "$OCF_RESKEY_mem_unit" in
        [Pp]) echo $(($(SysInfo_megabytes "$1") / 1024 / 1024 / 1024));;
        [Tt]) echo $(($(SysInfo_megabytes "$1") / 1024 / 1024));;
        [Gg]) echo $(($(SysInfo_megabytes "$1") / 1024));;
        [Mm]) echo "$(SysInfo_megabytes "$1")" ;;
        [Kk]) echo $(($(SysInfo_megabytes "$1") * 1024));;
        [Bb]) echo $(($(SysInfo_megabytes "$1") * 1024 * 1024));;
        *)
            ocf_log err "Invalid value for mem_unit: $OCF_RESKEY_mem_unit"
            echo $(($(SysInfo_megabytes "$1") / 1024));;
    esac
}

SysInfo_mem_units() {
    mem="$1"

    if [ -z "$1" ]; then
        return
    fi

    mem=$(SysInfo_megabytes "$1")
    # Round to the next multiple of 50
    r=$(($mem % 50))
    if [ $r -ne 0 ]; then
        mem=$(($mem + 50 - $r))
    fi

    echo $mem
}

SysInfo_megabytes() {
    # Size in megabytes
    echo $1 | awk '{ n = $0;
        sub( /[0-9]+(.[0-9]+)?/, "" );
        if ( $0 == "" ) { $0 = "G" }; # Do not change previous behavior `if ($0 == "G" || $0 == "") { n *= 1024 };`
        split( n, a, $0 );
        n = a[1];
        if ( /^[pP]i?[bB]?/ ) { n *= 1024 * 1024 * 1024 };
        if ( /^[tT]i?[bB]?/ ) { n *= 1024 * 1024 };
        if ( /^[gG]i?[bB]?/ ) { n *= 1024 };
        if ( /^[mM]i?[bB]?/ ) { n *= 1 };
        if ( /^[kK]i?[bB]?/ ) { n /= 1024 };
        if ( /^[bB]i?/ )      { n /= 1024 * 1024 };
        printf "%d\n", n }' # Intentionally round to an integer
}

healthmem_start() {
    modify_value mem_red 0 mem_yellow 0
    healthmem_monitor
    if [ $? -eq $OCF_SUCCESS ]; then
        return $OCF_SUCCESS
    fi
    touch "${OCF_RESKEY_state}"
}

healthmem_stop() {
    healthmem_monitor
    if [ $? -eq $OCF_SUCCESS ]; then
        rm "${OCF_RESKEY_state}"
    fi
    return $OCF_SUCCESS
}

healthmem_monitor() {
    if [ -f "${OCF_RESKEY_state}" ]; then
        name=`hostname`
	res=`cibadmin --query --xpath "//node_state[@uname='$name']//nvpair[@name='#health-mem']" | awk -F 'value=|/>' '{print $2}'`
        info_red=`get_value mem_red`
	info_yellow=`get_value mem_yellow`
	if [ "${info_red}" -eq 0 ] && [ "${res}" = '"red"' ];then
            send_alerts MEM red
	    modify_value mem_red 1 mem_yellow 0
        fi
        if [ "${info_yellow}" -eq 0 ] && [ "${res}" = '"yellow"' ];then
            send_alerts MEM yellow
            modify_value mem_red 0 mem_yellow 1
        fi
	if [ "${res}" = '"green"' ];then
            modify_value mem_red 0 mem_yellow 0
        fi
		
        if [ -f /proc/meminfo ]; then
            # meminfo results are in kB
            memfree=$(grep "MemFree:" /proc/meminfo | awk '{print $2}')
            buffers=$(grep "Buffers:" /proc/meminfo | awk '{print $2}')
            cached=$(grep "Cached:" /proc/meminfo | awk 'NR==1{print}' | awk '{print $2}')
            SReclaimable=$(grep "SReclaimable:" /proc/meminfo | awk '{print $2}')
            mem=$(($memfree+$buffers+$cached+$SReclaimable))"k"
            mem_total=$(grep "MemTotal" /proc/meminfo | awk '{print $2"k"}')
        fi

        if [ -n "$mem" ]; then
            # Massage the memory values
            mem=$(SysInfo_mem_units "$mem")
            mem_total=$(SysInfo_mem_units "$mem_total")
            UpdateStat ram_free $mem
            UpdateStat ram_total $mem_total
            hostname=`hostname`
            ram_free=`attrd_updater -n "ram_free" -Q --node "$hostname" | awk -F 'value=|"' '{print $7}'`
            ram_total=`attrd_updater -n "ram_total" -Q --node "$hostname" | awk -F 'value=|"' '{print $7}'`
            mem_free=`echo "scale=2;$ram_free*100/$ram_total" | bc`
            if [ -n "${OCF_RESKEY_red_mem}" ] && [ `echo "$mem_free <= ${OCF_RESKEY_red_mem}" | bc` -eq 1 ]; then
                    MEM_STATUS="red"
                    attrd_updater -n "#health-mem" -B "$MEM_STATUS" -d ${OCF_RESKEY_dampening}
                    return $OCF_SUCCESS
            fi
            if [ -n "${OCF_RESKEY_yellow_mem}" ] && [ `echo "$mem_free <= ${OCF_RESKEY_yellow_mem}" | bc` -eq 1 ]; then
                    MEM_STATUS="yellow"
                    attrd_updater -n "#health-mem" -B "$MEM_STATUS" -d ${OCF_RESKEY_dampening}
            fi

            if [ -n "${OCF_RESKEY_yellow_mem}" ] && [ `echo "$mem_free > ${OCF_RESKEY_yellow_mem}" | bc` -eq 1 ]; then
                    MEM_STATUS="green"
                    attrd_updater -n "#health-mem" -B "$MEM_STATUS" -d ${OCF_RESKEY_dampening}
            fi
        fi

        return $OCF_SUCCESS
    fi

    return $OCF_NOT_RUNNING
}

healthmem_reload_agent() {
    # No action required
    :;
}

healthmem_validate() {
    # Is the state directory writable?
    state_dir=$(dirname "$OCF_RESKEY_state")
    [ -d "$state_dir" ] && [ -w "$state_dir" ] && [ -x "$state_dir" ]
    if [ $? -ne 0 ]; then
        return $OCF_ERR_ARGS
    fi
    return $OCF_SUCCESS
}

: ${OCF_RESKEY_CRM_meta_interval:=0}
: ${OCF_RESKEY_CRM_meta_globally_unique:="false"}

if [ -z "$OCF_RESKEY_state" ]; then
    state="${HA_VARRUN%%/}/HealthMEM-${OCF_RESOURCE_INSTANCE}.state"
    if [ "${OCF_RESKEY_CRM_meta_globally_unique}" = "false" ]; then
        # Strip off the trailing clone marker
        OCF_RESKEY_state=$(echo $state | sed s/:[0-9][0-9]*\.state/.state/)
    else
        OCF_RESKEY_state="$state"
    fi
fi

if [ -z "${OCF_RESKEY_red_mem}" ] ; then
    OCF_RESKEY_red_mem=10
fi

if [ -z "${OCF_RESKEY_yellow_mem}" ] ; then
    OCF_RESKEY_yellow_mem=50
fi

if [ -z "${OCF_RESKEY_dampening}" ]; then
    OCF_RESKEY_dampening="0s"
fi

case "$__OCF_ACTION" in
meta-data)      meta_data
                exit $OCF_SUCCESS
                ;;
start)          healthmem_start;;
stop)           healthmem_stop;;
monitor)        healthmem_monitor;;
reload-agent)   healthmem_reload_agent;;
validate-all)   healthmem_validate;;
usage|help)     healthmem_usage
                exit $OCF_SUCCESS
                ;;
*)              healthmem_usage
                exit $OCF_ERR_UNIMPLEMENTED
                ;;
esac
rc=$?
ocf_log debug "${OCF_RESOURCE_INSTANCE} $__OCF_ACTION : $rc"
exit $rc

# vim: set filetype=sh expandtab tabstop=4 softtabstop=4 shiftwidth=4 textwidth=80:
