#
# copyright (c) 2000 by the Oracle Corporation
#
# NAME:
#
#  dgmetric.tcl : Event tcl file that checks an arbitrary condition
#                 against a data point colected by the data gatherer
#
# ARGUMENTS:
#           args(0) == event ID (<cartridge_id>_C<class_id>R<resource_id>)
#           args(1) == the connect credentials (<number> <parameter list>)
#           argv(2) == metric type
#           argv(3) == metric data type
#           argv(4) == target type
#           argv(5) == cartridge id
#           argv(6) == class id
#           argv(7) == resource id
#           argv(8) == instance
#           argv(9) == operator
#           argv(10) == no of occurrence
#           argv(11) == alert threshold used
#           argv(12) == alert threshold value
#           argv(13) == warning threshold used
#           argv(14) == warning threshold value
#           argv(15) == instance select flag
#           argv(16) == additional resource id (for process class)
#
# RETURN:
#           $SCRIPT_FAIL or
#           $CLEAR_EVENT or
#           $NO_EVENT or
#           $WARNING_EVENT or
#           $ALERT_EVENT
#
# OUTPUT:
#           The current value of the data point
#


# Event definition
oradefine event /oracle/generic/metric/dgmetric
oraarguments event_id connect_str metric_type data_type target_type cartridge_id class_id resource_id instance operator no_occurrence alert_used alert_threshold warning_used warning_threshold instance_select_flag additional_resource_id
oravardesc event_id string
oravardesc connect_str oracle_signon
oravardesc metric_type string
oravardesc data_type string
oravardesc target_type string
oravardesc cartridge_id string
oravardesc class_id string
oravardesc resource_id string
oravardesc instance string
oravardesc operator string
oravardesc no_occurrence unsigned
oravardesc alert_used string
oravardesc alert_threshold string
oravardesc warning_used string
oravardesc warning_threshold string
oravardesc instance_select_flag string
oravardesc additional_resource_id string
oraresults event_instances event_values
oravardesc event_instances string
oravardesc event_values string
oradefine end

# Initialize global variables
set output ""
set noc_list {}
set TargetAddress "discoverme"
set first_call 1
oraeventpersist last_alert_instances {}
oraeventpersist last_warning_instances {}

# check space in default instance "NT Operating System"
proc space_in_instance_name { cartridge_id class_id } {
    if { [string compare $cartridge_id "NT_OS"] == 0 &&
         ( $class_id == 0 || $class_id == 2 || $class_id == 3 || $class_id == 8 ) } {
        return 1
    } else {
        return 0
    }
}

# The main event checking functions
proc EvalEvent {} {

    # Declare globals we will use
    global argv last_report output noc_list first_call
    global SCRIPT_FAIL CLEAR_EVENT NO_EVENT WARNING_EVENT ALERT_EVENT
    global oramsg
    global last_alert_instances last_warning_instances
    global TargetAddress TargetUserdata

    # consume the input parameters
    set event_id [lindex $argv 0]
    set connect_str [lindex $argv 1]
    set metric_type [lindex $argv 2]
    set data_type [lindex $argv 3]
    set target_type [lindex $argv 4]
    set cartridge_id [lindex $argv 5]
    set class_id [lindex $argv 6]
    set resource_id [lindex $argv 7]
    set instance [lindex $argv 8]
    set operator [lindex $argv 9]
    set no_occurrence [lindex $argv 10]
    set alert_used [lindex $argv 11]
    set alert_threshold [lindex $argv 12]
    set warning_used [lindex $argv 13]
    set warning_threshold [lindex $argv 14]
    set instance_select_flag [lindex $argv 15]
    set additional_resource_id [lindex $argv 16]

    # initialize the return code and output (first element is the event_id)
    set ret_code $NO_EVENT
    set output $event_id
    set alert_instances {}
    set warning_instances {}

    # CSF: Begin TagetAddress, TargetUserdata discovery from services.ora

    # CSF:  get address and userdata fields from services.ora
    #       use target_name ($oramsg(oraobject)) and target_type
    #       to get unique entry out of services.ora.

    set TargetName [string trim $oramsg(oraobject)]
    set TargetType [lindex $argv 4]

    # CSF: only get globals TargetAddress, TargetUserdata first time
    if { ($TargetAddress == "discoverme") } {
      if { ($TargetType == "ORACLE_NODE")} {
        # set TargetAddress and TargetUserdata null for ORACLE_NODE targets
        set TargetAddress {}
        set TargetUserdata {}
      } else {
        set FoundTarget 0
        set orafile [concatname [list $oramsg(orahome) network agent services.ora]]
        ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : [oratime] : Trying to open $orafile"
        if {[file exists $orafile]} {
            if {[catch {set fd [open $orafile r]} err]} {
                # error opening services.ora
                ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : [oratime] : Error opening $orafile, error = $err"
                lappend output [format [msgtxt [NETWORK] nms 1005] dgmetric.tcl]
                lappend output [format [msgtxt [NETWORK] nms 312] services.ora]
                lappend output $err
                if {$last_report == $SCRIPT_FAIL} {
                    return $NO_EVENT
                } else {
                    set last_report $SCRIPT_FAIL
                    return $SCRIPT_FAIL
                }
            } else {
                # success, services.ora is open
                # look for entry with matching target_name and target_type
                ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : [oratime] : Looking for TargetName = $TargetName, TargetType = $TargetType"
                while {[gets $fd line] >= 0} {
                    ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : [oratime] : Found services.ora line = $line"

                    set tmpLine [split $line =]
                    set tmpTargetName [string trim [lindex $tmpLine 0]]
                    ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : [oratime] : comparing $tmpTargetName with $TargetName"
                    if {$tmpTargetName == $TargetName} {
                        # same target name. now check target_type
                        # get right hand side of "target = (type,node,address,userdata)"
                        # strip left and right parens () from entry leaving comma separated list type,node,address,userdata
                        set stripboth [string range $line [expr [string first ( $line] + 1] [expr [string last ) $line] - 1]]

                        if { [string trim [lindex [split $stripboth ,] 0]] == $TargetType } {
                            # We have the uniqe entry from services.ora in $line
                            set FoundTarget 1
                            set TargetAddress [string trim [lindex [split $stripboth ,] 2]]

                            # we need take whatever after the third "," as the user data
                            set tmp [string range $stripboth [expr [string first , $stripboth] + 1] end]
                            set tmp [string range $tmp [expr [string first , $tmp] + 1] end]
                            set tmp [string range $tmp [expr [string first , $tmp] + 1] end]
                            set TargetUserdata [string trim $tmp]

                            ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : [oratime] : Discovered TargetAddress = $TargetAddress, TargetUserdata = $TargetUserdata"
                            break
                        }
                    }
                }
                close $fd
            }
        } else {
            # services.ora does not exist: Can't happen unless it is removed after agent started.
            ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : [oratime] : fatal error: services.ora does not exist."
            lappend output [format [msgtxt [NETWORK] nms 1005] dgmetric.tcl]
            lappend output [format [msgtxt [NETWORK] nms 312] services.ora]
            if {$last_report == $SCRIPT_FAIL} {
                return $NO_EVENT
            } else {
                set last_report $SCRIPT_FAIL
                return $SCRIPT_FAIL
            }
        }

        if {$FoundTarget == 0} {
            # failure: did not find TargetName, TargetType in services.ora
            ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : [oratime] : fatal error: services.ora does not contain TargetName = $TargetName and TargetType = $TargetType"
            lappend output [format [msgtxt [NETWORK] nms 1005] dgmetric.tcl]
            lappend output [format [msgtxt [NETWORK] nms 1082] ]
            if {$last_report == $SCRIPT_FAIL} {
                return $NO_EVENT
            } else {
                set last_report $SCRIPT_FAIL
                return $SCRIPT_FAIL
            }
        }
      }
    }

    # CSF: End TagetAddress, TargetUserdata discovery from services.ora

    # xxu: create connect_str (special case for database target)
    set connect_str_org $connect_str
    set length [lindex $connect_str_org 0]
# note: for database target, last one is "role", which we do not need that for DG connection.
    if {[string compare $cartridge_id "DBA"] == 0} {
        set length [expr $length - 1]
    }
    set connect_str ""
    set i 1
    while {$i <= $length} {
        lappend connect_str [lindex $connect_str_org $i]
        incr i
    }
    lappend connect_str $oramsg(oraobject)

# if class is dynamic, instance must be -1
    if { $instance_select_flag == 0 } {
        set dg_instance "-1"
    } else {
        set dg_instance $instance
    }

# to handle space in default instance "NT Operating System"
    if { [space_in_instance_name $cartridge_id $class_id] } {
         set dg_instance [format "{%s}" $dg_instance]
    }

    if { [string compare $additional_resource_id "null"] } {
        set resource_id [list $resource_id $additional_resource_id]
    }

    if { [catch {oradggetdata $connect_str $TargetAddress $TargetUserdata $cartridge_id $class_id $resource_id $dg_instance} ret] } {
        set first_call 0
        ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : catched error, ret = $ret"
        if {$ret == "Info: A new event has been registered"} {
            ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : [oratime] : event has been registered."
            return $NO_EVENT
        } elseif {$ret == "Info: no data available."} {
            ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : [oratime] : no data returned from cartridge."
            return $NO_EVENT
        } elseif {$ret == "Error: failed to acquire cartridge state."} {
            ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : [oratime] : failed to acquire cartridge state for $cartridge_id."
            lappend output [format [msgtxt [NETWORK] nms 1005] dgmetric.tcl]
            lappend output [format [msgtxt [NETWORK] nms 1022] $cartridge_id]
            if {$last_report == $SCRIPT_FAIL} {
                return $NO_EVENT
            } else {
                set last_report $SCRIPT_FAIL
                return $SCRIPT_FAIL
            }
        } elseif {$ret == "Error: failed to initialize cartridge for an event."} {
            ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : [oratime] : failed to initialize cartridge for an event."
            lappend output [format [msgtxt [NETWORK] nms 1005] dgmetric.tcl]
            lappend output [format [msgtxt [NETWORK] nms 1023] $cartridge_id]
            if {$last_report == $SCRIPT_FAIL} {
                return $NO_EVENT
            } else {
                set last_report $SCRIPT_FAIL
                return $SCRIPT_FAIL
            }
        } elseif {$ret == "Error: class specified does not exist"} {
            ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : [oratime] : class id $class_id does not exist."
            lappend output [format [msgtxt [NETWORK] nms 1005] dgmetric.tcl]
            lappend output [format [msgtxt [NETWORK] nms 1024] $class_id $cartridge_id]
            if {$last_report == $SCRIPT_FAIL} {
                return $NO_EVENT
            } else {
                set last_report $SCRIPT_FAIL
                return $SCRIPT_FAIL
            }
        } elseif {$ret == "Error: dynamic class instance can only be '-1'"} {
            ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : [oratime] : dynamic class, instance name $instance_name != '-1'."
            lappend output [format [msgtxt [NETWORK] nms 1005] dgmetric.tcl]
            lappend output [format [msgtxt [NETWORK] nms 1025] ]
            if {$last_report == $SCRIPT_FAIL} {
                return $NO_EVENT
            } else {
                set last_report $SCRIPT_FAIL
                return $SCRIPT_FAIL
            }
        } elseif {$ret == "Error: static class cannot have the '-1' instance"} {
            ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : [oratime] : static class, instance name $instance_name == '-1'."
            lappend output [format [msgtxt [NETWORK] nms 1005] dgmetric.tcl]
            lappend output [format [msgtxt [NETWORK] nms 1026] ]
            if {$last_report == $SCRIPT_FAIL} {
                return $NO_EVENT
            } else {
                set last_report $SCRIPT_FAIL
                return $SCRIPT_FAIL
            }
        } elseif {$ret == "Error: failed to get instance names for a class"} {
            ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : [oratime] : failed to get instance names for class $class_id."
            lappend output [format [msgtxt [NETWORK] nms 1005] dgmetric.tcl]
            lappend output [format [msgtxt [NETWORK] nms 1027] $class_id]
            if {$last_report == $SCRIPT_FAIL} {
                return $NO_EVENT
            } else {
                set last_report $SCRIPT_FAIL
                return $SCRIPT_FAIL
            }
        } elseif {$ret == "Error: requested instance not known by cartridge"} {
            ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : [oratime] : instance $instance does not exist."
            lappend output [format [msgtxt [NETWORK] nms 1005] dgmetric.tcl]
            lappend output [format [msgtxt [NETWORK] nms 1028] $instance]
            if {$last_report == $SCRIPT_FAIL} {
                return $NO_EVENT
            } else {
                set last_report $SCRIPT_FAIL
                return $SCRIPT_FAIL
            }
        } else {
            # we got some other errors
            # what error is this and can we really afford to not signal it ever?
            ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : [oratime] : oradggetdata returns Error: $ret"
            lappend output [format [msgtxt [NETWORK] nms 1005] dgmetric.tcl]
            lappend output $ret
            if {$last_report == $SCRIPT_FAIL} {
                return $NO_EVENT
            } else {
                set last_report $SCRIPT_FAIL
                return $SCRIPT_FAIL
            }
        }
    } else {
        if { $first_call == 1 } {
            set first_call 0
            ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : [oratime] : event has been registered."
            return $NO_EVENT
        }
        set dg_returned $ret
    }

# retrive the number of occurrences array
    set loop [llength $noc_list]
    set i 0
    while {$i < $loop} {
        set tmp [lindex $noc_list $i]
        set last_noc_alert([lindex $tmp 0]) [lindex [lindex $tmp 1] 0]
        set last_noc_warning([lindex $tmp 0]) [lindex [lindex $tmp 1] 1]
        incr i
    }

# cartridge may return value NULL for rate data in the first sample
# variable all_null_value is used to check if "NULL" is returned for all instances.
# if all instances got "NULL", we like to return NO_EVENT instead of CLEAR_EVENT
    set all_null_value 1

    # evaluate the current value of the datapoint
    set noc_list {}
    set ret_length [llength $dg_returned]
    set i 0
    while {$i < $ret_length} {
        set value [lindex $dg_returned $i]

# for dynamic instances, filter on instance name 
        if { $instance_select_flag == 0 && [string compare $instance "*"] } {
            set cur_instance [lindex $value 0]
            set instance_found ""

            foreach tmp_instance $instance {
                if { ![string compare $cur_instance $tmp_instance] } {
                    set instance_found $tmp_instance
                    break
                }
            }

            if { ![string compare $instance_found ""] } {
                ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : [oratime] : instance $cur_instance skipped."
                incr i
                continue
            }
        }

        if { [string compare $additional_resource_id "null"] } {
            set tmp_pid [lindex [split [lindex $value 2] "."] 0]
            set this_instance [format "%s:%s" [lindex $value 0] $tmp_pid]
        } else {
            set this_instance [lindex $value 0]
        }

# OMS cannot handle space in default instance "NT Operating System"
# so replace "NT Operating System" with TargetName (which is host name for this case)
        if { [space_in_instance_name $cartridge_id $class_id] } {
            set this_instance $TargetName
        }

        set this_value($this_instance) [lindex $value 1]

        ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : [oratime] : instance = $this_instance, value = $this_value($this_instance)"

# for multiple instances, "NULL" will be returned for invalid instance
        if { ![string compare $this_value($this_instance) "NULL"] } {
            ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : [oratime] : no value for instance $this_instance."
            incr i
            continue
        }

# we get at lease one instance has some value
        set all_null_value 0

        set alert_reset 1
        set warning_reset 1
        # evaluate the current severity
        if { $alert_used == 1 } {
            set condition [format "expr %s %s %s" $this_value($this_instance) $operator $alert_threshold]
            if { [catch {eval $condition} ret] } {
                # Tcl error in eval
                ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : [oratime] : ret = $ret"
                ORATCL_DEBUG "WARNING: cartridge returned <$this_value($this_instance)> for instance $this_instance, eval <$condition> failed. Skip."
                incr i
                continue
            }
            if { $ret } {
                if {[catch {incr last_noc_alert($this_instance)} ret1]} {
                    set last_noc_alert($this_instance) 1
                }
                set alert_reset 0
            }
        }
        if { $warning_used == 1 } {
            set condition [format "expr %s %s %s" $this_value($this_instance) $operator $warning_threshold]
            if { [catch {eval $condition} ret] } {
                # Tcl error in eval
                ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : [oratime] : ret = $ret"
                ORATCL_DEBUG "WARNING: cartridge returned <$this_value($this_instance)> for instance $this_instance, eval <$condition> failed. Skip."
                incr i
                continue
            }
            if { $ret } {
                if { [catch {incr last_noc_warning($this_instance)} ret1] } {
                    set last_noc_warning($this_instance) 1
                }
                set warning_reset 0
            }
        }
        if { $alert_reset } {
            set last_noc_alert($this_instance) 0
        }
        if { $warning_reset } {
           set last_noc_warning($this_instance) 0
        }

        if { $alert_reset == 0 || $warning_reset == 0 } {
            lappend noc_list [list $this_instance [list $last_noc_alert($this_instance) $last_noc_warning($this_instance)]]
        }

        incr i
    }

# if $all_null_value == 1, it means that no available data returned from cartridge.
# we should return NO_EVENT
    if { $all_null_value } {
        return $NO_EVENT
    }

# check to see the number of occurrences match the set
    set number_alerts 0
    set number_warnings 0

    set loop [llength $noc_list]

# if the noc array is empty, none of the instances has hit the threshold values
    if { $loop == 0 } {
        set ret_code $CLEAR_EVENT
        set last_alert_instances {}
        set last_warning_instances {}

        if { $last_report == $ret_code } {
            return $NO_EVENT
        } else {
            set last_report $ret_code
            ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : [oratime] : MESSAGE - $ret_code"
            return $ret_code
        }
    }

    set i 0
    while { $i < $loop } {
        set this_instance [lindex [lindex $noc_list $i] 0]
        set alert_set 0
        if { $alert_used == 1 } {
            if { $last_noc_alert($this_instance) >= $no_occurrence } {
                set alert_set 1
                set ret_code $ALERT_EVENT
                lappend alert_instances $this_instance
                lappend alert_values $this_value($this_instance)
                incr number_alerts
            }
        }
        if { $warning_used == 1 } {
            if { $last_noc_warning($this_instance) >= $no_occurrence } {
                if { $ret_code != $ALERT_EVENT } {
                    set ret_code $WARNING_EVENT
                }
                if { $alert_set == 0} {
                    lappend warning_instances $this_instance
                    lappend warning_values $this_value($this_instance)
                    incr number_warnings
                }
            }
        }
        incr i
    }

    if { $number_alerts > 0 } {
        ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : [oratime] : "
        ORATCL_DEBUG "        alert_instances = $alert_instances, values = $alert_values"
    }
    if { $number_warnings > 0 } {
        ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : [oratime] : "
        ORATCL_DEBUG "        warning_instances = $warning_instances, values = $warning_values"
    }

    # return
    if { $ret_code == $last_report &&
         [string compare $alert_instances $last_alert_instances] == 0 &&
         [string compare $warning_instances $last_warning_instances] == 0 } {
        return $NO_EVENT
    } else {
# create output
        set number_of_instances 0
        set max_instances 20
        set event_instances {}

        for { set i 0 } { $i < $number_alerts } { incr i } {
            lappend event_instances [lindex $alert_instances $i]
            lappend event_values [lindex $alert_values $i]
            incr number_of_instances
            if { $number_of_instances == $max_instances } {
                break;
            }
        }

        if { $number_of_instances < $max_instances } {
            for { set i 0 } { $i < $number_warnings } { incr i } {
                lappend event_instances [lindex $warning_instances $i]
                lappend event_values [lindex $warning_values $i]
                incr number_of_instances
                if { $number_of_instances == $max_instances } {
                    break;
                }
            }
        }

        if { $number_of_instances == $max_instances } {
            lappend event_instances ...
            lappend event_values ...
        }

        lappend output $event_instances $event_values

        ORATCL_DEBUG "dgmetric($event_id) : $oramsg(oraobject) : [oratime] : MESSAGE - $ret_code, output = $output"

        set last_report $ret_code
        set last_alert_instances $alert_instances
        set last_warning_instances $warning_instances
        return $ret_code
    }

}

