# Monitorizando Dataguard en Bash

Cuántas veces nos hemos encontrado con una integración de monitorización cuando entra en juego DG?

Pues lo voy a poner fácil. Lo hacemos en un script con las recomendaciones de Oracle. La versión con la que trabajo es 19c, importante.

En este caso está planificado en nagios, pero sería perfectamente válido para otras plataformas como zabbix. Al final lo que importa es la salida ( Ok, Warning, Critical) y/o el exit code.

La idea es que pasándole el SID pueda distinguir si es Primaria o Standby y poder planificar el script para Standby y Primaria y que solo salte cuando se cumpla el rol para el chequeo.

Las alertas query\_apply\_lag y query\_transport\_lag están parametrizadas para saltar a los 5 minutos, es ajustable al entorno.

Os presento uno de los Carli-scripts que os facilitará la vida. Empezamos?

```plaintext
check_dg.sh
check_dg.sh <ORACLE_SID> <query_archive_dest|query_gaps|query_errors|query_loging|query_apply_lag|query_transport_lag|query_datafiles_offline|query_mrp
```

Tiene buena pinta verdad? Espera que invoquemos con el SID y el chequeo concreto.

En qué me baso?

[Monitoring Oracle Data Guard Configuration Health Using SQL 19c](https://docs.oracle.com/en/database/oracle/oracle-database/19/haovw/monitor-oracle-data-guard-configuration.html)

Para ello he desarollado el siguiente script:

```plaintext
[nagios@rac-prueba-001 ~]$ cat /home/oracle/check_dg.sh
#!/bin/bash
# Verificar que se haya pasado ORACLE_SID como argumento
if [ "$#" -ne 2 ]; then
    echo "Uso: $0 <ORACLE_SID> <query_archive_dest|query_gaps|query_errors|query_loging|query_apply_lag|query_transport_lag|query_datafiles_offline|query_mrp>"
    exit 1
fi


#Variables entorno de BBDD
ORACLE_SID=$1
ORAENV_ASK=NO
. oraenv -s

#Variable chequeo
OPCION_CHECK=$2

# Configuración de conexión
dbname=$ORACLE_SID  # El nombre de la base de datos será el mismo que ORACLE_SID
dbuser='system'
dbpass='Temporal01'

# Función para ejecutar un comando SQL y devolver los resultados
ejecutar_sqlplus() {
    local sql=$1
    $ORACLE_HOME/bin/sqlplus -s "$dbuser/$dbpass@$dbname" <<EOF
	SET PAGESIZE 0
	SET FEEDBACK OFF
	SET VERIFY OFF
	SET HEADING OFF
	SET ECHO OFF
	$sql
	EXIT;
EOF
}


# Verificar si la base de datos es primaria
query_role=$(cat <<EOF
SELECT DATABASE_ROLE FROM v\$database;
EOF
)

#################### STANDBY ##########################

# Consulta de chequeo lag apply
query_apply_lag=$(cat <<EOF
        SELECT name || ': ' || max(value)
        FROM gv\$dataguard_stats
        WHERE   name = 'apply lag'
                AND TO_DSINTERVAL(value) > NUMTODSINTERVAL(15,'MINUTE')
        GROUP BY NAME;
EOF
    )

# Consulta de chequeo lag transport
query_transport_lag=$(cat <<EOF
        SELECT name || ': ' || max(value)
        FROM gv\$dataguard_stats
        WHERE   name = 'transport lag'
                AND TO_DSINTERVAL(value) > NUMTODSINTERVAL(15,'MINUTE')
        GROUP BY NAME;
EOF
    )


# Data file check (offline files or files that are not accessible)
query_datafiles_offline=$(cat <<EOF
	select 'Hay ' || count(*) || ' datafiles en estado offline'
 	from v\$datafile_header
 	where 	status ='OFFLINE'
 		or ERROR is not null
        having count(*)>0;
EOF
    )

# Verify that the Media Recovery Process is currently running. Tiene que devolver fila
query_mrp=$(cat <<EOF
	select count(*)
 	from gv\$managed_standby
 	where process like 'MRP%'
        having count(*)>0;
EOF
    )

#################### PRIMARIA ########################

# Consulta de chequeo errores arhive dest
query_archive_dest=$(cat <<EOF
	WITH AggregatedData AS (
    		SELECT inst_id, substr(error,1,9) error
    		from gv\$archive_dest_status
    		where type='PHYSICAL'
    	and status!='VALID'
	)
	SELECT 'Instancia: ' || LISTAGG(inst_id, ',') WITHIN GROUP (ORDER BY inst_id) || ' Errores: ' || LISTAGG(error, ',') WITHIN GROUP (ORDER BY error) AS result
	FROM AggregatedData
        HAVING COUNT(*) > 0;
EOF
    )

# Consulta de chequeo gaps
query_gaps=$(cat <<EOF
	select LISTAGG(inst_id, ',') WITHIN GROUP (ORDER BY inst_id) || ' ' ||  database_mode || ' ' || recovery_mode || ' ' || gap_status
 	from gv\$archive_dest_status
 	where 	type='PHYSICAL'
 		and gap_status !='NO GAP'
	group by database_mode,recovery_mode, gap_status;

EOF
    )

# severe Data Guard event occurred in the last day	
query_errors=$(cat <<EOF
	WITH AggregatedData AS (
  	SELECT inst_id, COUNT(*) AS count
  	FROM gv\$dataguard_status
  	WHERE 	severity IN ('Error', 'Fatal')
    		AND timestamp > (sysdate - 1)
  	GROUP BY inst_id
	)
	SELECT 'Instancia: ' || LISTAGG(inst_id, ',') WITHIN GROUP (ORDER BY inst_id) || ' Numero errores: ' || LISTAGG(count, ',') WITHIN GROUP (ORDER BY inst_id) AS result
	FROM AggregatedData
        HAVING COUNT(*) > 0;
EOF
    )

# Check if any NOLOGGING activity occurred on the primary database in the last day	
query_loging=$(cat <<EOF
	SELECT CASE
         WHEN COUNT(*) > 0 THEN 'Hay datafiles con NOLOGGING activity en el último día. File ID: ' ||
                                  LISTAGG(file#, ',') WITHIN GROUP (ORDER BY file#)
         ELSE NULL
       	END AS result
	FROM v\$datafile
	WHERE unrecoverable_time > (sysdate - 1);
EOF
    )

role_output=$(ejecutar_sqlplus "$query_role")

if [ "$role_output" = "PRIMARY" ]; then

    case $OPCION_CHECK in
        query_archive_dest)
    		check_output=$(ejecutar_sqlplus "$query_archive_dest")
    		if [ -n "$check_output" ]; then
        		echo "[CRITICAL] $check_output"
                        exit 2
    		fi

            ;;
        query_gaps)
                check_output=$(ejecutar_sqlplus "$query_gaps")
                if [ -n "$check_output" ]; then
                        echo "[CRITICAL] $check_output"
                        exit 2
                fi

            ;;
        query_errors)
                check_output=$(ejecutar_sqlplus "$query_errors")
                if [ -n "$check_output" ]; then
                        echo "[CRITICAL] $check_output"
 			exit 2
                fi

            ;;
        query_loging)
                check_output=$(ejecutar_sqlplus "$query_loging")
                if [ -n "$check_output" ]; then
                        echo "[WARNING] $check_output"
			exit 1
                fi

            ;;
        query_datafiles_offline)
                check_output=$(ejecutar_sqlplus "$query_datafiles_offline")
                if [ -n "$check_output" ]; then
                        echo "[WARNING] $check_output"
                        exit 1
                fi

            ;;
    esac
fi

if [ "$role_output" = "PHYSICAL STANDBY" ]; then
    case $OPCION_CHECK in
        query_apply_lag)
                check_output=$(ejecutar_sqlplus "$query_apply_lag")
                if [ -n "$check_output" ]; then
                        echo "[CRITICAL] $check_output"
			exit 2
                fi

            ;;
        query_transport_lag)
                check_output=$(ejecutar_sqlplus "$query_transport_lag")
                if [ -n "$check_output" ]; then
                        echo "[CRITICAL] $check_output"
			exit 2
                fi

            ;;
        query_datafiles_offline)
                check_output=$(ejecutar_sqlplus "$query_datafiles_offline")
                if [ -n "$check_output" ]; then
                        echo "[WARNING] $check_output"
			exit 1
                fi

            ;;
        query_mrp)
                check_output=$(ejecutar_sqlplus "$query_mrp")
                if [ -n "$check_output" ]; then
 			echo "[OK]"
                        exit 0
                else
			echo "[CRITICAL]. No hay procesos activos de replicación."
			exit 2
                fi

            ;;

    esac

fi

echo "[OK]"
exit 0
```

Qué esperamos? Devolverá un OK si todo está en orden, WARNING o CRITICAL junto con la información asociada en una sola línea cuando no es así. Es totalmente customizable.

Lo pruebas y me cuentas?

```plaintext
[usuario_monitorizacion@rac-prueba-001 ~]$ /home/oracle/check_dg.sh cdbpro query_archive_dest
[OK]
```
