Sunday, May 4, 2025

http://blog.tanelpoder.com/files/scripts/ash/ashtop.sql sql_id,u.username,event "sql_plan_operation='TABLE ACCESS' and sql_plan_options='FULL'" sysdate-1/24 sysdate @ashtop sql_id,p1,p2,p3 "event like '%TM%'" sysdate-1/24 sysdate with stats as ( select /*+ materialize */ * from ( select parsing_schema_name,t.start#,t.end#,t.sql_id,t.plan_hash_value,t.execs,t.avg_sec,s.sql_text from ( select s.parsing_schema_name,min(begin_interval_time) start#,max(begin_interval_time) end#, sql_id, plan_hash_value, sum(nvl(executions_delta,0)) execs, round ((sum(elapsed_time_delta)/(sum(executions_delta)))/1000000) avg_sec from DBA_HIST_SQLSTAT S, DBA_HIST_SNAPSHOT SS where 1=1 and ss.begin_interval_time > trunc(sysdate-4,'iw') and ss.snap_id = S.snap_id and ss.instance_number = S.instance_number and executions_delta > 0 and (elapsed_time_delta/decode(nvl(executions_delta,0),0,1,executions_delta))/1000000 > &1 group by sql_id, plan_hash_value,parsing_schema_name )t join dba_hist_sqltext s on s.sql_id=t.sql_id order by execs*avg_sec desc ) tt where rownum <= 50 ) select * from stats s where parsing_schema_name not like 'SYS%' and s.sql_text not like '%/* DS_SVC */%' and s.sql_text not like '%_job_proc%' and s.sql_text not like '%SQL Analyze%' and lower(s.sql_text) not like '%dbms_feature%' and s.sql_text not like '%SYS_DBA_SEGS%' and lower(s.sql_text) not like '%v$sys%' and lower(s.sql_text) not like '%dba_audit_trail%' and lower(s.sql_text) not like '%no_index(mytab)%'; select t.start#,t.end#,t.sql_id,t.plan_hash_value,t.execs,t.avg_sec,s.sql_text from ( select min(begin_interval_time) start#,max(begin_interval_time) end#, sql_id, plan_hash_value, sum(nvl(executions_delta,0)) execs, round ((sum(elapsed_time_delta)/(sum(executions_delta)))/1000000) avg_sec from DBA_HIST_SQLSTAT S, DBA_HIST_SNAPSHOT SS where 1=1 and ss.snap_id = S.snap_id and ss.instance_number = S.instance_number and executions_delta > 0 and (elapsed_time_delta/decode(nvl(executions_delta,0),0,1,executions_delta))/1000000 > 5 group by sql_id, plan_hash_value )t join dba_hist_sqltext s on s.sql_id=t.sql_id order by execs desc; select cast(min (ash.SAMPLE_TIME) as date) as start# ,round (24*60*(cast (max(ash.SAMPLE_TIME) as date) - cast(min (ash.SAMPLE_TIME) as date) ),2) as duration# ,ash.sql_id,ash.top_level_sql_id,ash.BLOCKING_SESSION as B_SID,ash.BLOCKING_SESSION_SERIAL# as b_serial# ,ash2.SQL_EXEC_ID b_sql_exec_id ,ash.event,do.object_name ,sum(decode(ash.session_state,'ON CPU',1,0)) "CPU" ,sum(decode(ash.session_state,'WAITING',1,0)) - sum(decode(ash.session_state,'WAITING', decode(ash.wait_class, 'User I/O',1,0),0)) "WAIT" ,sum(decode(ash.session_state,'WAITING', decode(ash.wait_class, 'User I/O',1,0),0)) "IO" ,sum(decode(ash.session_state,'ON CPU',1,1)) "TOTAL" ,du.username,ash2.SQL_EXEC_ID, dp.owner||nvl2(dp.object_name,'.'||dp.object_name,null) ||nvl2(dp.procedure_name,'.'||dp.procedure_name,null) as pl_sql_obj ,ash2.machine as blocking_machine from dba_hist_active_sess_history ash left join dba_objects do on do.object_id=ash.CURRENT_OBJ# join dba_hist_active_sess_history ash2 on ash.BLOCKING_SESSION=ash2.session_id and ash.BLOCKING_SESSION_SERIAL#=ash2.session_serial# and ash.SNAP_ID=ash2.SNAP_ID join dba_users du on du.USER_ID=ash2.USER_ID left join dba_procedures dp on dp.object_id=ash2.PLSQL_ENTRY_OBJECT_ID and dp.subprogram_id=ash.PLSQL_ENTRY_SUBPROGRAM_ID where ash.SQL_ID is not NULL and ash.SAMPLE_TIME > trunc(sysdate) group by ash.SQL_EXEC_ID,ash2.SQL_EXEC_ID, ash2.machine, ash.session_id,ash.session_serial#,ash.event,ash.sql_id,ash.top_level_sql_id,ash.BLOCKING_SESSION,ash.BLOCKING_SESSION_SERIAL#, ash2.sql_id ,du.username, dp.owner||nvl2(dp.object_name,'.'||dp.object_name,null) ||nvl2(dp.procedure_name,'.'||dp.procedure_name,null) ,do.object_name having sum(decode(ash.session_state,'WAITING',1,0)) - sum(decode(ash.session_state,'WAITING', decode(ash.wait_class, 'User I/O',1,0),0)) >0 and max(ash.SAMPLE_TIME) - min (ash.SAMPLE_TIME) > interval '3' minute order by 1,ash2.sql_exec_id; col object_name for a35 col cnt for 99999 SELECT cnt, object_name, object_type,file#, dbablk, obj, tch, hladdr FROM ( select count(*) cnt, rfile, block from ( SELECT /*+ ORDERED USE_NL(l.x$ksuprlat) */ --l.laddr, u.laddr, u.laddrx, u.laddrr, dbms_utility.data_block_address_file(to_number(object,'XXXXXXXX')) rfile, dbms_utility.data_block_address_block(to_number(object,'XXXXXXXX')) block FROM (SELECT /*+ NO_MERGE */ 1 FROM DUAL CONNECT BY LEVEL <= 100000) s, (SELECT ksuprlnm LNAME, ksuprsid sid, ksuprlat laddr, TO_CHAR(ksulawhy,'XXXXXXXXXXXXXXXX') object FROM x$ksuprlat) l, (select indx, kslednam from x$ksled ) e, (SELECT indx , ksusesqh sqlhash , ksuseopc , ksusep1r laddr FROM x$ksuse) u WHERE LOWER(l.Lname) LIKE LOWER('%cache buffers chains%') AND u.laddr=l.laddr AND u.ksuseopc=e.indx AND e.kslednam like '%cache buffers chains%' ) group by rfile, block ) objs, x$bh bh, dba_objects o WHERE bh.file#=objs.rfile and bh.dbablk=objs.block and o.object_id=bh.obj order by cnt ; SET PAGESIZE 100 SET LINESIZE 200 COLUMN begin_time FORMAT A20 COLUMN instance_name FORMAT A20 COLUMN cpu_count FORMAT 99 COLUMN aas FORMAT 999.99 COLUMN status FORMAT A10 PROMPT === DATABASE LOAD - AAS BASED ON DELTA DB TIME (CORRECTED) === WITH db_time_data AS ( SELECT s.snap_id, s.instance_number, MIN(s.begin_interval_time) AS begin_time, MAX(s.end_interval_time) AS end_time, SUM(tm.value) AS db_time FROM dba_hist_sys_time_model tm JOIN dba_hist_snapshot s ON tm.snap_id = s.snap_id AND tm.instance_number = s.instance_number WHERE tm.stat_name = 'DB time' AND s.begin_interval_time > SYSDATE - &DAYS_AGO GROUP BY s.snap_id, s.instance_number ), cpu_cores AS ( SELECT instance_number, MAX(VALUE) AS cpu_count FROM dba_hist_osstat WHERE stat_name = 'NUM_CPUS' GROUP BY instance_number ), instance_names AS ( SELECT DISTINCT instance_number, instance_name FROM gv$instance ) SELECT TO_CHAR(d.begin_time, 'YYYY-MM-DD HH24:MI') AS begin_time, i.instance_name, c.cpu_count, ROUND((d.db_time / 1000000) / ((CAST(d.end_time AS DATE) - CAST(d.begin_time AS DATE)) * 24 * 60 * 60), 2) AS aas, CASE WHEN ROUND((d.db_time / 1000000) / ((CAST(d.end_time AS DATE) - CAST(d.begin_time AS DATE)) * 24 * 60 * 60), 2) > c.cpu_count * 1.0 THEN 'CRITICAL' WHEN ROUND((d.db_time / 1000000) / ((CAST(d.end_time AS DATE) - CAST(d.begin_time AS DATE)) * 24 * 60 * 60), 2) > c.cpu_count * 0.75 THEN 'WARNING' ELSE 'OK' END AS status FROM db_time_data d JOIN cpu_cores c ON d.instance_number = c.instance_number LEFT JOIN instance_names i ON d.instance_number = i.instance_number ORDER BY d.begin_time DESC, i.instance_name; ## 58_rac_iops_trend.sql PROMPT === 1-DAY IOPS TREND PER INSTANCE (AWR: DBA_HIST_SYSSTAT) === SET PAGESIZE 100 SET LINESIZE 200 COLUMN snap_time FORMAT A20 COLUMN instance_number FORMAT 99 COLUMN iops FORMAT 999999.99 WITH io_stats AS ( SELECT s.snap_id, s.instance_number, TO_CHAR(s.begin_interval_time, 'YYYY-MM-DD HH24:MI') AS snap_time, MAX(CASE WHEN ss.stat_name = 'physical read IO requests' THEN ss.value END) AS read_io, MAX(CASE WHEN ss.stat_name = 'physical write IO requests' THEN ss.value END) AS write_io, s.begin_interval_time, s.end_interval_time FROM dba_hist_sysstat ss JOIN dba_hist_snapshot s ON ss.snap_id = s.snap_id AND ss.instance_number = s.instance_number WHERE ss.stat_name IN ('physical read IO requests', 'physical write IO requests') AND s.begin_interval_time > SYSDATE - 1 GROUP BY s.snap_id, s.instance_number, s.begin_interval_time, s.end_interval_time ), deltas AS ( SELECT snap_time, instance_number, (read_io + write_io) - LAG(read_io + write_io) OVER (PARTITION BY instance_number ORDER BY begin_interval_time) AS total_io, (CAST(end_interval_time AS DATE) - CAST(begin_interval_time AS DATE)) * 24 * 60 * 60 AS elapsed_seconds FROM io_stats ) SELECT snap_time, instance_number, ROUND(total_io / NULLIF(elapsed_seconds, 0), 2) AS iops FROM deltas WHERE total_io IS NOT NULL ORDER BY snap_time DESC, instance_number; PROMPT === REAL-TIME IOPS PER INSTANCE (GV$SYSSTAT) === SET PAGESIZE 100 SET LINESIZE 200 COLUMN inst_id FORMAT 99 COLUMN name FORMAT A40 COLUMN value FORMAT 999999999 COLUMN iops FORMAT 999999 SELECT inst_id, 'IOPS (Read+Write)' AS name, reads + writes AS value, ROUND((reads + writes) / 60) AS iops -- assuming ~60 sec snapshot FROM ( SELECT inst_id, SUM(CASE WHEN name LIKE 'physical read IO requests' THEN value ELSE 0 END) AS reads, SUM(CASE WHEN name LIKE 'physical write IO requests' THEN value ELSE 0 END) AS writes FROM gv$sysstat WHERE name IN ('physical read IO requests', 'physical write IO requests') GROUP BY inst_id ); File Name Description 53_rac_wait_skew.sql RAC node wait skew detection from ASH PROMPT === TOP WAIT EVENTS PER INSTANCE (RAC SKEW CHECK) === SET PAGESIZE 100 SET LINESIZE 200 COLUMN inst_id FORMAT 99 COLUMN event FORMAT A50 COLUMN wait_count FORMAT 9999999 SELECT inst_id, event, COUNT(*) AS wait_count FROM gv$active_session_history WHERE sample_time > SYSDATE - 5/1440 AND session_state = 'WAITING' GROUP BY inst_id, event ORDER BY inst_id, wait_count DESC; 54_exadata_smart_scan_stats.sql Smart scan stats from V$SYSSTAT PROMPT === EXADATA SMART SCAN UTILIZATION (V$SYSSTAT) === SET PAGESIZE 100 SET LINESIZE 200 COLUMN name FORMAT A50 COLUMN value FORMAT 999999999 SELECT name, value FROM v$sysstat WHERE name LIKE 'cell smart%scan%'; 55_interconnect_latency.sql Exadata interconnect performance PROMPT === INTERCONNECT LATENCY (GV$CELL_GLOBAL_STATISTICS) === SET PAGESIZE 100 SET LINESIZE 200 COLUMN inst_id FORMAT 99 COLUMN name FORMAT A60 COLUMN value_mb FORMAT 999999999.99 SELECT inst_id, name, ROUND(value/1024/1024, 2) AS value_mb FROM gv$cell_global_statistics WHERE name IN ( 'CLO read retries due to stalling', 'IO bytes sent via Smart Interconnect to cells', 'IO bytes sent via non-Smart Interconnect to cells' ) ORDER BY inst_id, name; 56_gc_contention.sql Global cache contention (gc waits) PROMPT === RAC GLOBAL CACHE CONTENTION EVENTS (GV$EVENT_HISTOGRAM) === SET PAGESIZE 100 SET LINESIZE 200 COLUMN inst_id FORMAT 99 COLUMN event FORMAT A50 COLUMN wait_time_milli FORMAT 999 COLUMN wait_count FORMAT 999999 SELECT inst_id, event, wait_time_milli, wait_count FROM gv$event_histogram WHERE event IN ( 'gc buffer busy acquire', 'gc buffer busy release', 'gc cr block busy' ) AND wait_count > 0 ORDER BY inst_id, event, wait_time_milli; 57_exadata_offload_ratio.sql Offload vs logical I/O ratio from AWR PROMPT === EXADATA OFFLOAD SQL STATS (AWR: DBA_HIST_SQLSTAT) === SET PAGESIZE 100 SET LINESIZE 200 COLUMN sql_id FORMAT A15 COLUMN offload_gb FORMAT 999999.99 COLUMN logical_gb FORMAT 999999.99 COLUMN offload_ratio FORMAT 9.99 SELECT sql_id, SUM(cell_offload_elig_bytes) / 1024 / 1024 / 1024 AS offload_gb, SUM(cell_uncompressed_bytes) / 1024 / 1024 / 1024 AS logical_gb, ROUND(SUM(cell_offload_elig_bytes) / NULLIF(SUM(cell_uncompressed_bytes), 0), 2) AS offload_ratio FROM dba_hist_sqlstat WHERE snap_id IN ( SELECT snap_id FROM dba_hist_snapshot WHERE begin_interval_time > SYSDATE - 1 ) GROUP BY sql_id ORDER BY offload_ratio DESC NULLS LAST; SET PAGESIZE 100 SET PAGESIZE 100 SET LINESIZE 200 COLUMN begin_time FORMAT A20 COLUMN instance_name FORMAT A20 COLUMN cpu_count FORMAT 99 COLUMN db_time FORMAT 999999999 COLUMN db_time_mins FORMAT 999999.99 COLUMN aas FORMAT 999.99 COLUMN status FORMAT A10 PROMPT === DATABASE LOAD (AAS PER MINUTE FROM AWR DELTAS) - ORACLE RAC === WITH dbtime_deltas AS ( SELECT s.instance_number, s.snap_id, s.begin_interval_time, s.end_interval_time, tm.value - LAG(tm.value) OVER (PARTITION BY s.instance_number ORDER BY s.snap_id) AS db_time FROM dba_hist_snapshot s JOIN dba_hist_sys_time_model tm ON s.snap_id = tm.snap_id AND s.instance_number = tm.instance_number WHERE tm.stat_name = 'DB time' AND s.begin_interval_time > SYSDATE - &DAYS_AGO ), cpu_cores AS ( SELECT instance_number, MAX(value) AS cpu_count FROM dba_hist_osstat WHERE stat_name = 'NUM_CPUS' GROUP BY instance_number ), instance_names AS ( SELECT DISTINCT inst_id AS instance_number, instance_name FROM gv$instance ), load_data AS ( SELECT d.begin_interval_time, d.end_interval_time, i.instance_name, c.cpu_count, d.db_time, ROUND(d.db_time / 1e6, 2) AS db_time_mins, ROUND((d.db_time / 1e6) / ((CAST(d.end_interval_time AS DATE) - CAST(d.begin_interval_time AS DATE)) * 24 * 60), 2) AS aas, CASE WHEN ROUND((d.db_time / 1e6) / ((CAST(d.end_interval_time AS DATE) - CAST(d.begin_interval_time AS DATE)) * 24 * 60), 2) > c.cpu_count THEN 'CRITICAL' WHEN ROUND((d.db_time / 1e6) / ((CAST(d.end_interval_time AS DATE) - CAST(d.begin_interval_time AS DATE)) * 24 * 60), 2) > c.cpu_count * 0.75 THEN 'WARNING' ELSE 'OK' END AS status FROM dbtime_deltas d JOIN cpu_cores c ON d.instance_number = c.instance_number LEFT JOIN instance_names i ON d.instance_number = i.instance_number WHERE d.db_time IS NOT NULL ) SELECT TO_CHAR(begin_interval_time, 'YYYY-MM-DD HH24:MI') AS begin_time, instance_name, cpu_count, db_time, db_time_mins, aas, status FROM load_data ORDER BY begin_interval_time DESC, instance_name; PROMPT PROMPT === TOTAL AAS PER MINUTE ACROSS ALL INSTANCES === SELECT TO_CHAR(begin_interval_time, 'YYYY-MM-DD HH24:MI') AS begin_time, ROUND(SUM(aas), 2) AS total_aas FROM load_data GROUP BY begin_interval_time ORDER BY begin_interval_time DESC; PROMPT PROMPT === TOTAL AAS PER MINUTE ACROSS ALL INSTANCES === SELECT TO_CHAR(begin_interval_time, 'YYYY-MM-DD HH24:MI') AS begin_time, ROUND(SUM(aas), 2) AS total_aas FROM load_data GROUP BY begin_interval_time ORDER BY begin_interval_time DESC; SET PAGESIZE 100 SET LINESIZE 200 COLUMN begin_time FORMAT A20 COLUMN instance_name FORMAT A20 COLUMN cpu_count FORMAT 99 COLUMN db_time_mins FORMAT 999999.99 COLUMN aas FORMAT 999.99 COLUMN status FORMAT A10 PROMPT === DATABASE LOAD (AAS PER MINUTE FROM AWR) - ORACLE RAC === WITH db_time_data AS ( SELECT s.snap_id, s.instance_number, s.begin_interval_time, s.end_interval_time, MAX(CASE WHEN tm.stat_name = 'DB time' THEN tm.value END) AS db_time FROM dba_hist_sys_time_model tm JOIN dba_hist_snapshot s ON tm.snap_id = s.snap_id AND tm.instance_number = s.instance_number WHERE s.begin_interval_time > SYSDATE - &DAYS_AGO AND tm.stat_name = 'DB time' GROUP BY s.snap_id, s.instance_number, s.begin_interval_time, s.end_interval_time ), cpu_cores AS ( SELECT instance_number, MAX(value) AS cpu_count FROM dba_hist_osstat WHERE stat_name = 'NUM_CPUS' GROUP BY instance_number ), instance_names AS ( SELECT DISTINCT inst_id AS instance_number, instance_name FROM gv$instance ) SELECT TO_CHAR(d.begin_interval_time, 'YYYY-MM-DD HH24:MI') AS begin_time, i.instance_name, c.cpu_count, ROUND(d.db_time / 1e6, 2) AS db_time_mins, ROUND((d.db_time / 1e6) / ((CAST(d.end_interval_time AS DATE) - CAST(d.begin_interval_time AS DATE)) * 24 * 60), 2) AS aas, CASE WHEN ROUND((d.db_time / 1e6) / ((CAST(d.end_interval_time AS DATE) - CAST(d.begin_interval_time AS DATE)) * 24 * 60), 2) > c.cpu_count THEN 'CRITICAL' WHEN ROUND((d.db_time / 1e6) / ((CAST(d.end_interval_time AS DATE) - CAST(d.begin_interval_time AS DATE)) * 24 * 60), 2) > c.cpu_count * 0.75 THEN 'WARNING' ELSE 'OK' END AS status FROM db_time_data d JOIN cpu_cores c ON d.instance_number = c.instance_number LEFT JOIN instance_names i ON d.instance_number = i.instance_number ORDER BY d.begin_interval_time DESC, i.instance_name; is_exadata() { local db_connect="$1" # Query V$VERSION or V$PARAMETER to detect Exadata features local result result=$(sqlplus -s "$db_connect" < 50 THEN 'CRITICAL' ELSE 'OK' END AS status FROM gv$active_session_history WHERE event LIKE 'ges%' AND sample_time > SYSDATE - (&HOURS_AGO / 24) GROUP BY event ORDER BY samples DESC; 47 SET PAGESIZE 100 SET LINESIZE 200 COLUMN inst_id FORMAT 99 COLUMN name FORMAT A40 COLUMN mb FORMAT 999999.99 COLUMN status FORMAT A10 PROMPT === HIGH INTERCONNECT ACTIVITY (GV$SYSSTAT) === SELECT inst_id, name, ROUND(value / 1024 / 1024, 2) AS mb, CASE WHEN value > 500000000 THEN 'CRITICAL' ELSE 'OK' END AS status FROM gv$sysstat WHERE name IN ( 'gc current blocks received', 'gc cr blocks received', 'gc current blocks served', 'gc cr blocks served' ) ORDER BY inst_id, name; 46 SET PAGESIZE 100 SET LINESIZE 200 COLUMN blocking_session FORMAT 99999 COLUMN blocking_inst_id FORMAT 99 COLUMN blocks FORMAT 9999 COLUMN first_seen FORMAT A20 COLUMN last_seen FORMAT A20 COLUMN status FORMAT A10 PROMPT === GES BLOCKING EVENTS (GV$ACTIVE_SESSION_HISTORY) === SELECT blocking_session, blocking_inst_id, COUNT(*) AS blocks, TO_CHAR(MIN(sample_time), 'YYYY-MM-DD HH24:MI') AS first_seen, TO_CHAR(MAX(sample_time), 'YYYY-MM-DD HH24:MI') AS last_seen, CASE WHEN COUNT(*) > 20 THEN 'CRITICAL' ELSE 'OK' END AS status FROM gv$active_session_history WHERE blocking_session IS NOT NULL AND sample_time > SYSDATE - (&HOURS_AGO / 24) GROUP BY blocking_session, blocking_inst_id ORDER BY blocks DESC; 45 SET PAGESIZE 100 SET LINESIZE 200 COLUMN inst_id FORMAT 99 COLUMN event FORMAT A40 COLUMN count FORMAT 99999 COLUMN status FORMAT A10 PROMPT === GC WAITS BY INSTANCE (GV$ACTIVE_SESSION_HISTORY) === SELECT inst_id, event, COUNT(*) AS count, CASE WHEN COUNT(*) > 500 THEN 'CRITICAL' WHEN COUNT(*) > 200 THEN 'WARNING' ELSE 'OK' END AS status FROM gv$active_session_history WHERE event LIKE 'gc%' AND sample_time > SYSDATE - (&HOURS_AGO / 24) GROUP BY inst_id, event ORDER BY inst_id, count DESC;45 44 SET PAGESIZE 100 SET LINESIZE 200 COLUMN event FORMAT A40 COLUMN samples FORMAT 99999 COLUMN pct FORMAT 999.99 COLUMN status FORMAT A10 PROMPT === TOP GLOBAL CACHE WAITS (GV$ACTIVE_SESSION_HISTORY) === SELECT event, COUNT(*) AS samples, ROUND(COUNT(*) * 100 / SUM(COUNT(*)) OVER (), 2) AS pct, CASE WHEN event LIKE 'gc%' AND COUNT(*) > 100 THEN 'CRITICAL' ELSE 'OK' END AS status FROM gv$active_session_history WHERE event LIKE 'gc%' AND sample_time > SYSDATE - (&HOURS_AGO / 24) GROUP BY event ORDER BY samples DESC FETCH FIRST 10 ROWS ONLY; 43 SET PAGESIZE 100 SET LINESIZE 200 COLUMN inst_id FORMAT 99 COLUMN cpu_busy_secs FORMAT 999999.99 COLUMN total_cpu_secs FORMAT 999999.99 COLUMN cpu_util_pct FORMAT 999.99 COLUMN status FORMAT A10 PROMPT === REAL-TIME CPU UTILIZATION (GV$OSSTAT) - ORACLE 19C RAC === WITH os_stat AS ( SELECT inst_id, MAX(CASE WHEN stat_name = 'BUSY_TIME' THEN value END) AS busy_time, MAX(CASE WHEN stat_name = 'IDLE_TIME' THEN value END) AS idle_time FROM gv$osstat WHERE stat_name IN ('BUSY_TIME', 'IDLE_TIME') GROUP BY inst_id ) SELECT inst_id, ROUND(busy_time / 100, 2) AS cpu_busy_secs, ROUND((busy_time + idle_time) / 100, 2) AS total_cpu_secs, ROUND((busy_time / (busy_time + idle_time)) * 100, 2) AS cpu_util_pct, CASE WHEN (busy_time / (busy_time + idle_time)) * 100 > 90 THEN 'CRITICAL' WHEN (busy_time / (busy_time + idle_time)) * 100 > 75 THEN 'WARNING' ELSE 'OK' END AS status FROM os_stat ORDER BY inst_id; 42 SET PAGESIZE 100 SET LINESIZE 200 COLUMN inst_id FORMAT 99 COLUMN cpu_count FORMAT 99 COLUMN aas FORMAT 999.99 COLUMN status FORMAT A10 PROMPT === REAL-TIME AAS (GV$ACTIVE_SESSION_HISTORY, LAST &MINUTES_AGO MINUTES) === WITH active_sessions AS ( SELECT inst_id, COUNT(*) / (&MINUTES_AGO * 60) AS aas FROM gv$active_session_history WHERE sample_time > SYSDATE - (&MINUTES_AGO / 1440) AND session_type = 'FOREGROUND' GROUP BY inst_id ), cpu_cores AS ( SELECT instance_number AS inst_id, MAX(value) AS cpu_count FROM dba_hist_osstat WHERE stat_name = 'NUM_CPUS' GROUP BY instance_number ) SELECT a.inst_id, c.cpu_count, ROUND(a.aas, 2) AS aas, CASE WHEN a.aas > c.cpu_count THEN 'CRITICAL' WHEN a.aas > c.cpu_count * 0.75 THEN 'WARNING' ELSE 'OK' END AS status FROM active_sessions a JOIN cpu_cores c ON a.inst_id = c.inst_id ORDER BY a.inst_id; SET PAGESIZE 100 SET LINESIZE 200 COLUMN service_name FORMAT A25 COLUMN inst_id FORMAT 99 COLUMN session_type FORMAT A10 COLUMN active_count FORMAT 99999 COLUMN cpu_count FORMAT 99 COLUMN status FORMAT A10 PROMPT === ACTIVE SESSIONS PER INSTANCE (SCALED TO CPU CORES) - ORACLE 19C RAC === WITH session_stats AS ( SELECT inst_id, service_hash, session_type, COUNT(CASE WHEN session_state IN ('ON CPU','WAITING') THEN 1 END) AS active_count FROM gv$active_session_history WHERE sample_time > SYSDATE - &DAYS_AGO GROUP BY inst_id, service_hash, session_type ), cpu_cores AS ( SELECT instance_number AS inst_id, MAX(value) AS cpu_count FROM dba_hist_osstat WHERE stat_name = 'NUM_CPUS' GROUP BY instance_number ), services AS ( SELECT name_hash, name AS service_name FROM gv$services ) SELECT NVL(s.service_name, 'Unknown') AS service_name, ss.inst_id, ss.session_type, ss.active_count, c.cpu_count, CASE WHEN ss.active_count > c.cpu_count THEN 'CRITICAL' WHEN ss.active_count > c.cpu_count * 0.75 THEN 'WARNING' ELSE 'OK' END AS status FROM session_stats ss JOIN cpu_cores c ON ss.inst_id = c.inst_id LEFT JOIN services s ON ss.service_hash = s.name_hash ORDER BY ss.inst_id, service_name; ##### # Example modules run_sql_file_report "DB Load (AAS)" "$SQL_DIR/01_db_load.sql" "$DB_CONNECT_STRING" run_sql_file_report "Long Running Sessions" "$SQL_DIR/06_long_running_sessions.sql" "$DB_CONNECT_STRING" run_sql_file_report "IO Response Time" "$SQL_DIR/07_io_response_time.sql" "$DB_CONNECT_STRING" if is_exadata; then run_sql_file_report "Exadata Offload Efficiency" "$SQL_DIR/35_exadata_offload_efficiency.sql" "$DB_CONNECT_STRING" run_sql_file_report "Exadata Cell Interconnect Waits" "$SQL_DIR/36_exadata_cell_interconnect_waits.sql" "$DB_CONNECT_STRING" run_sql_file_report "Exadata Flash Cache Stats" "$SQL_DIR/37_exadata_flashcache_stats.sql" "$DB_CONNECT_STRING" run_sql_file_report "Exadata Smart Scan Usage" "$SQL_DIR/38_exadata_smart_scan_usage.sql" "$DB_CONNECT_STRING" run_sql_file_report "ASM Diskgroup Status" "$SQL_DIR/39_exadata_asm_diskgroup_status.sql" "$DB_CONNECT_STRING" run_sql_file_report "Exadata Wait Class Usage" "$SQL_DIR/40_exadata_wait_class_usage.sql" "$DB_CONNECT_STRING" run_sql_file_report "IORM Plan Check" "$SQL_DIR/41_exadata_iorm_plan.sql" "$DB_CONNECT_STRING" fi echo "" >> "$HTML_REPORT" SET PAGESIZE 100 SET LINESIZE 200 COLUMN begin_time FORMAT A20 COLUMN instance_name FORMAT A20 COLUMN cpu_count FORMAT 99 COLUMN db_time_mins FORMAT 999999.99 COLUMN aas FORMAT 999.99 COLUMN status FORMAT A10 PROMPT === DATABASE LOAD (AAS AUTO-SCALED BY CPU CORES PER INSTANCE) - ORACLE 19C RAC === WITH db_time_data AS ( SELECT s.snap_id, s.instance_number, s.begin_interval_time, s.end_interval_time, MAX(CASE WHEN tm.stat_name = 'DB time' THEN tm.value END) AS db_time FROM dba_hist_sys_time_model tm JOIN dba_hist_snapshot s ON tm.snap_id = s.snap_id AND tm.instance_number = s.instance_number WHERE s.begin_interval_time > SYSDATE - &DAYS_AGO AND tm.stat_name = 'DB time' GROUP BY s.snap_id, s.instance_number, s.begin_interval_time, s.end_interval_time ), cpu_cores AS ( SELECT instance_number, MAX(VALUE) AS cpu_count FROM dba_hist_osstat WHERE stat_name = 'NUM_CPUS' GROUP BY instance_number ), instance_names AS ( SELECT DISTINCT instance_number, instance_name FROM gv$instance ) SELECT TO_CHAR(d.begin_interval_time, 'YYYY-MM-DD HH24:MI') AS begin_time, i.instance_name, c.cpu_count, ROUND(d.db_time / 1000000 / 60, 2) AS db_time_mins, ROUND((d.db_time / 1000000 / 60) / ((CAST(d.end_interval_time AS DATE) - CAST(d.begin_interval_time AS DATE)) * 24 * 60), 2) AS aas, CASE WHEN ROUND((d.db_time / 1000000 / 60) / ((CAST(d.end_interval_time AS DATE) - CAST(d.begin_interval_time AS DATE)) * 24 * 60), 2) > c.cpu_count * 1.0 THEN 'CRITICAL' WHEN ROUND((d.db_time / 1000000 / 60) / ((CAST(d.end_interval_time AS DATE) - CAST(d.begin_interval_time AS DATE)) * 24 * 60), 2) > c.cpu_count * 0.75 THEN 'WARNING' ELSE 'OK' END AS status FROM db_time_data d JOIN cpu_cores c ON d.instance_number = c.instance_number LEFT JOIN instance_names i ON d.instance_number = i.instance_number ORDER BY d.begin_interval_time DESC, i.instance_name; if is_exadata; then run_sql_file_report "Exadata Offload Efficiency" "$SQL_DIR/35_exadata_offload_efficiency.sql" "$DB_CONNECT_STRING" run_sql_file_report "Exadata Cell Interconnect Waits" "$SQL_DIR/36_exadata_cell_interconnect_waits.sql" "$DB_CONNECT_STRING" run_sql_file_report "Exadata Flash Cache Stats" "$SQL_DIR/37_exadata_flashcache_stats.sql" "$DB_CONNECT_STRING" run_sql_file_report "Exadata Smart Scan Usage" "$SQL_DIR/38_exadata_smart_scan_usage.sql" "$DB_CONNECT_STRING" run_sql_file_report "ASM Diskgroup Status" "$SQL_DIR/39_exadata_asm_diskgroup_status.sql" "$DB_CONNECT_STRING" run_sql_file_report "Exadata Wait Class Usage" "$SQL_DIR/40_exadata_wait_class_usage.sql" "$DB_CONNECT_STRING" run_sql_file_report "IORM Plan Check" "$SQL_DIR/41_exadata_iorm_plan.sql" "$DB_CONNECT_STRING" fi PROMPT === EXADATA CELL OFFLOAD EFFICIENCY === SELECT name, value, CASE WHEN name = 'cell physical IO interconnect bytes returned by smart scan' AND value = 0 THEN 'CRITICAL' ELSE 'OK' END AS status FROM v$sysstat WHERE name IN ( 'cell physical IO interconnect bytes returned by smart scan', 'cell physical IO bytes eligible for predicate offload' ); ## PROMPT === INTERCONNECT-RELATED WAITS (EXADATA) === SELECT event, total_waits, time_waited_micro/1000000 AS time_secs, CASE WHEN event LIKE 'cell%' AND time_waited_micro > 1000000 THEN 'WARNING' ELSE 'OK' END AS status FROM v$system_event WHERE event LIKE 'cell%' ORDER BY time_waited_micro DESC FETCH FIRST 10 ROWS ONLY; ## PROMPT === FLASH CACHE STATISTICS (EXADATA) === SELECT name, value FROM v$sysstat WHERE name LIKE '%flash cache%' ORDER BY name; ## PROMPT === SMART SCAN USAGE CHECK (RECENT SQLs) === SELECT sql_id, offload_eligibility, offload_returned_bytes/1024/1024 AS returned_mb, offload_eligible_bytes/1024/1024 AS eligible_mb, CASE WHEN offload_eligibility = 'NONE' THEN 'WARNING' ELSE 'OK' END AS status FROM v$sql_monitor WHERE offload_eligibility IS NOT NULL AND last_refresh_time > SYSDATE - 1/24 ORDER BY last_refresh_time DESC FETCH FIRST 10 ROWS ONLY; ## PROMPT === ASM DISKGROUP SPACE & STATE === SELECT name, total_mb, free_mb, state, ROUND((free_mb/total_mb)*100, 2) AS pct_free, CASE WHEN state != 'MOUNTED' THEN 'CRITICAL' WHEN (free_mb/total_mb)*100 < 10 THEN 'WARNING' ELSE 'OK' END AS status FROM v$asm_diskgroup; PROMPT === EXADATA-SPECIFIC WAITS (GV$SESSION) === SELECT inst_id, wait_class, COUNT(*) AS count FROM gv$session WHERE wait_class IN ('Exadata', 'User I/O') GROUP BY inst_id, wait_class; ## PROMPT === IORM PLAN CHECK === SELECT plan_name, active, objective, status FROM v$cell_iorm_plan WHERE active = 'YES'; ## SET PAGESIZE 100 SET LINESIZE 200 COLUMN begin_time FORMAT A20 COLUMN instance_name FORMAT A20 COLUMN db_time_secs FORMAT 999999.99 COLUMN cpu_time_secs FORMAT 999999.99 COLUMN io_mb FORMAT 999999.99 COLUMN skew_status FORMAT A10 PROMPT === RAC INSTANCE SKEW ANALYSIS (DB TIME, CPU TIME, I/O) - ORACLE 19C === WITH workload AS ( SELECT s.snap_id, s.begin_interval_time, s.instance_number, MAX(CASE WHEN tm.stat_name = 'DB time' THEN tm.value END)/1000000 AS db_time_secs, MAX(CASE WHEN tm.stat_name = 'DB CPU' THEN tm.value END)/1000000 AS cpu_time_secs FROM dba_hist_sys_time_model tm JOIN dba_hist_snapshot s ON tm.snap_id = s.snap_id AND tm.instance_number = s.instance_number WHERE s.begin_interval_time > SYSDATE - &DAYS_AGO AND tm.stat_name IN ('DB time', 'DB CPU') GROUP BY s.snap_id, s.begin_interval_time, s.instance_number ), io_stats AS ( SELECT ss.snap_id, ss.instance_number, (SUM(CASE WHEN ss.stat_name IN ('physical read bytes', 'physical write bytes') THEN ss.value ELSE 0 END)/1024/1024) AS io_mb FROM dba_hist_sysstat ss WHERE ss.stat_name IN ('physical read bytes', 'physical write bytes') GROUP BY ss.snap_id, ss.instance_number ), instance_names AS ( SELECT DISTINCT instance_number, instance_name FROM gv$instance ), combined AS ( SELECT w.snap_id, w.begin_interval_time, w.instance_number, w.db_time_secs, w.cpu_time_secs, COALESCE(i.io_mb, 0) AS io_mb FROM workload w LEFT JOIN io_stats i ON w.snap_id = i.snap_id AND w.instance_number = i.instance_number ) SELECT TO_CHAR(c.begin_interval_time, 'YYYY-MM-DD HH24:MI') AS begin_time, i.instance_name, ROUND(c.db_time_secs, 2) AS db_time_secs, ROUND(c.cpu_time_secs, 2) AS cpu_time_secs, ROUND(c.io_mb, 2) AS io_mb, CASE WHEN c.db_time_secs > (SELECT AVG(db_time_secs)*1.5 FROM combined c2 WHERE c2.begin_interval_time = c.begin_interval_time) THEN 'CRITICAL' WHEN c.db_time_secs > (SELECT AVG(db_time_secs)*1.2 FROM combined c2 WHERE c2.begin_interval_time = c.begin_interval_time) THEN 'WARNING' WHEN c.io_mb > (SELECT AVG(io_mb)*1.5 FROM combined c3 WHERE c3.begin_interval_time = c.begin_interval_time) THEN 'CRITICAL' WHEN c.io_mb > (SELECT AVG(io_mb)*1.2 FROM combined c3 WHERE c3.begin_interval_time = c.begin_interval_time) THEN 'WARNING' ELSE 'OK' END AS skew_status FROM combined c LEFT JOIN instance_names i ON c.instance_number = i.instance_number ORDER BY c.begin_interval_time DESC, i.instance_name; SET PAGESIZE 100 SET LINESIZE 200 COLUMN inst_id FORMAT 99 COLUMN parameter FORMAT A30 COLUMN value FORMAT A40 COLUMN status FORMAT A10 PROMPT === EXADATA IORM CONFIGURATION (GV$CELL_CONFIG) === SELECT inst_id, parameter, value, CASE WHEN LOWER(parameter) = 'iormplanstatus' AND LOWER(value) LIKE '%active%' THEN 'OK' ELSE 'INFO' END AS status FROM gv$cell_config WHERE LOWER(parameter) IN ('iormplan', 'iormplanstatus', 'iormplanobject') ORDER BY inst_id, parameter; SET PAGESIZE 100 SET LINESIZE 200 COLUMN name FORMAT A30 COLUMN value FORMAT A30 COLUMN status FORMAT A10 PROMPT === EXADATA IORM CONFIGURATION (GV$CELL_CONFIG) === SELECT inst_id, name, value, CASE WHEN name = 'iormPlanStatus' AND LOWER(value) LIKE '%active%' THEN 'OK' ELSE 'INFO' END AS status FROM gv$cell_config WHERE name IN ('iormPlanObject', 'iormPlanStatus', 'iormPlan') ORDER BY inst_id, name; 52_awr_exadata_smart_scan.sql SET PAGESIZE 100 SET LINESIZE 200 COLUMN sql_id FORMAT A15 COLUMN elapsed_s FORMAT 999999.99 COLUMN execs FORMAT 99999 COLUMN buffer_gets FORMAT 999999999 COLUMN disk_reads FORMAT 99999999 COLUMN status FORMAT A10 PROMPT === AWR SMART SCAN INSIGHT (LAST 1 DAY FROM DBA_HIST_SQLSTAT) === SELECT s.sql_id, ROUND(SUM(s.elapsed_time_delta)/1e6, 2) AS elapsed_s, SUM(s.executions_delta) AS execs, SUM(s.buffer_gets_delta) AS buffer_gets, SUM(s.disk_reads_delta) AS disk_reads, CASE WHEN SUM(s.disk_reads_delta) > 0 AND SUM(s.buffer_gets_delta)/SUM(s.disk_reads_delta) < 5 THEN 'OK' ELSE 'WARNING' END AS status FROM dba_hist_sqlstat s WHERE s.snap_id IN ( SELECT snap_id FROM dba_hist_snapshot WHERE begin_interval_time > SYSDATE - 1 ) GROUP BY s.sql_id ORDER BY elapsed_s DESC FETCH FIRST 10 ROWS ONLY; 32_sga_pga_advisory.sql SET PAGESIZE 100 SET LINESIZE 200 COLUMN target_mb FORMAT 99999 COLUMN est_extra_rw_mb FORMAT 99999.99 COLUMN est_cache_hit FORMAT 999.99 COLUMN advice FORMAT A10 PROMPT === PGA TARGET ADVICE (Oracle 19c) === SELECT ROUND(pga_target_for_estimate / 1024 / 1024) AS target_mb, ROUND(estd_extra_bytes_rw / 1024 / 1024, 2) AS est_extra_rw_mb, estd_pga_cache_hit_percentage AS est_cache_hit, CASE WHEN estd_pga_cache_hit_percentage >= 99 THEN 'OK' WHEN estd_pga_cache_hit_percentage >= 90 THEN 'WARNING' ELSE 'CRITICAL' END AS advice FROM v$pga_target_advice WHERE estd_pga_cache_hit_percentage IS NOT NULL ORDER BY target_mb; PROMPT PROMPT === SGA TARGET ADVICE (Oracle 19c) === SELECT ROUND(sga_size / 1024) AS target_mb, estd_db_time / 100 AS est_db_time_seconds, estd_physical_reads, CASE WHEN estd_db_time <= MIN(estd_db_time) OVER () THEN 'OK' WHEN estd_db_time <= MIN(estd_db_time) OVER () * 1.1 THEN 'WARNING' ELSE 'CRITICAL' END AS advice FROM v$sga_target_advice WHERE sga_size_factor BETWEEN 0.5 AND 2 ORDER BY sga_size; ## SET PAGESIZE 100 SET LINESIZE 200 COLUMN sql_id FORMAT A15 COLUMN executions FORMAT 9999999 COLUMN cpu_time FORMAT 9999999.99 COLUMN disk_reads FORMAT 9999999 COLUMN module FORMAT A20 COLUMN avg_cpu FORMAT 999999.99 COLUMN avg_io FORMAT 999999.99 SET PAGESIZE 100 SET LINESIZE 200 COLUMN sql_id FORMAT A15 COLUMN sql_text FORMAT A50 COLUMN elapsed_time FORMAT 999999.99 COLUMN io_interconnect_bytes FORMAT 999999999 COLUMN status FORMAT A10 PROMPT === EXADATA SMART SCAN USAGE (RECENT SQLS) === SELECT sql_id, SUBSTR(sql_text, 1, 50) AS sql_text, elapsed_time / 1000000 AS elapsed_time, io_interconnect_bytes, CASE WHEN io_interconnect_bytes > 0 THEN 'OK' ELSE 'WARNING' END AS status FROM v$sql_monitor WHERE io_interconnect_bytes IS NOT NULL AND last_refresh_time > SYSDATE - 1/24 ORDER BY last_refresh_time DESC FETCH FIRST 10 ROWS ONLY; SET PAGESIZE 100 SET LINESIZE 200 COLUMN sql_id FORMAT A15 COLUMN elapsed_s FORMAT 999999.99 COLUMN execs FORMAT 99999 COLUMN cell_io_mb FORMAT 9999999.99 COLUMN smart_scans FORMAT 99999 COLUMN status FORMAT A10 PROMPT === AWR SMART SCAN USAGE (LAST 1 DAY) VIA DBA_HIST_SQLSTAT === SELECT s.sql_id, ROUND(s.elapsed_time_total/1e6, 2) AS elapsed_s, s.executions_total AS execs, ROUND(ss.value/1024/1024, 2) AS cell_io_mb, ss2.value AS smart_scans, CASE WHEN ss.value > 0 AND ss2.value > 0 THEN 'OK' ELSE 'WARNING' END AS status FROM dba_hist_sqlstat s JOIN dba_hist_sqltext t ON s.sql_id = t.sql_id LEFT JOIN ( SELECT sql_id, SUM(value) AS value FROM dba_hist_sqlstat s, dba_hist_sqlstat_name n WHERE s.stat_id = n.stat_id AND n.name = 'cell physical IO interconnect bytes' GROUP BY sql_id ) ss ON s.sql_id = ss.sql_id LEFT JOIN ( SELECT sql_id, SUM(value) AS value FROM dba_hist_sqlstat s, dba_hist_sqlstat_name n WHERE s.stat_id = n.stat_id AND n.name = 'cell smart table scan' GROUP BY sql_id ) ss2 ON s.sql_id = ss2.sql_id WHERE s.begin_interval_time > SYSDATE - 1 ORDER BY cell_io_mb DESC FETCH FIRST 10 ROWS ONLY; PROMPT === TOP 5 SQLs BY DISK READS === SELECT * FROM ( SELECT sql_id, executions_delta AS executions, disk_reads_delta AS disk_reads, ROUND(disk_reads_delta / NULLIF(executions_delta,0), 2) AS avg_io, module FROM dba_hist_sqlstat WHERE executions_delta > 0 ORDER BY disk_reads_delta DESC ) WHERE ROWNUM <= 5; PROMPT PROMPT === TOP 5 SQLs BY CPU TIME === SELECT * FROM ( SELECT sql_id, executions_delta AS executions, cpu_time_delta/1000000 AS cpu_time, ROUND((cpu_time_delta/1000000)/NULLIF(executions_delta,0), 2) AS avg_cpu, module FROM dba_hist_sqlstat WHERE executions_delta > 0 ORDER BY cpu_time_delta DESC ) WHERE ROWNUM <= 5; ##34 SET PAGESIZE 100 SET LINESIZE 200 COLUMN sql_id FORMAT A15 COLUMN executions FORMAT 9999999 COLUMN cpu_time FORMAT 9999999.99 COLUMN avg_cpu FORMAT 999999.99 COLUMN module FORMAT A20 PROMPT === TOP 5 SQLs BY CPU TIME === SELECT * FROM ( SELECT sql_id, executions_delta AS executions, cpu_time_delta/1000000 AS cpu_time, ROUND((cpu_time_delta/1000000)/NULLIF(executions_delta, 0), 2) AS avg_cpu, module FROM dba_hist_sqlstat WHERE executions_delta > 0 ORDER BY cpu_time_delta DESC ) WHERE ROWNUM <= 5; SET PAGESIZE 100 SET LINESIZE 200 COLUMN target_mb FORMAT 99999 COLUMN est_extra_rw_mb FORMAT 99999.99 COLUMN est_cache_hit FORMAT 999.99 COLUMN advice FORMAT A10 PROMPT === PGA TARGET ADVICE === SELECT ROUND(pga_target_for_estimate / 1024 / 1024) AS target_mb, ROUND(estd_extra_bytes_rw / 1024 / 1024, 2) AS est_extra_rw_mb, estd_pga_cache_hit_percentage AS est_cache_hit, CASE WHEN estd_pga_cache_hit_percentage >= 99 THEN 'OK' WHEN estd_pga_cache_hit_percentage >= 90 THEN 'WARNING' ELSE 'CRITICAL' END AS advice FROM v$pga_target_advice WHERE estd_pga_cache_hit_percentage IS NOT NULL ORDER BY target_mb; SET PAGESIZE 100 SET LINESIZE 200 COLUMN username FORMAT A20 COLUMN status FORMAT A10 COLUMN osuser FORMAT A15 COLUMN machine FORMAT A30 COLUMN terminal FORMAT A15 COLUMN logon_time FORMAT A20 PROMPT === FAILED SESSIONS OR LOGIN ERRORS (LAST 1 HOUR) === SELECT s.username, s.osuser, s.machine, s.terminal, TO_CHAR(s.logon_time, 'YYYY-MM-DD HH24:MI:SS') AS logon_time, CASE WHEN s.username IS NULL THEN 'FAILED' ELSE 'OK' END AS status FROM gv$session s WHERE s.logon_time > SYSDATE - 1/24 AND s.username IS NULL ORDER BY s.logon_time DESC; 23 SET PAGESIZE 100 SET LINESIZE 200 COLUMN inst_id FORMAT 99 COLUMN sid FORMAT 99999 COLUMN serial# FORMAT 99999 COLUMN username FORMAT A15 COLUMN object_name FORMAT A30 COLUMN object_type FORMAT A20 COLUMN status FORMAT A10 PROMPT === DATABASE OBJECT LOCKING DETAILS (GV$LOCK + DBA_OBJECTS) === SELECT s.inst_id, s.sid, s.serial#, s.username, o.object_name, o.object_type, CASE WHEN l.lmode IN (4, 5, 6) THEN 'LOCKED' ELSE 'REQUEST' END AS status FROM gv$session s JOIN gv$lock l ON s.sid = l.sid AND s.inst_id = l.inst_id JOIN dba_objects o ON l.id1 = o.object_id WHERE s.username IS NOT NULL AND l.type = 'TX' ORDER BY s.inst_id, s.sid; 49_ddl_object_locks.sql Reports DDL-related locks SET PAGESIZE 100 SET LINESIZE 200 COLUMN inst_id FORMAT 99 COLUMN sid FORMAT 99999 COLUMN serial# FORMAT 99999 COLUMN username FORMAT A15 COLUMN type FORMAT A10 COLUMN mode_held FORMAT A20 COLUMN object_name FORMAT A30 COLUMN object_type FORMAT A20 PROMPT === DDL OBJECT LOCKS (GV$LOCK + DBA_OBJECTS) === SELECT s.inst_id, s.sid, s.serial#, s.username, l.type, DECODE(l.lmode, 0, 'None', 1, 'Null', 2, 'Row-S (SS)', 3, 'Row-X (SX)', 4, 'Share', 5, 'S/Row-X (SSX)', 6, 'Exclusive', 'Other') AS mode_held, o.object_name, o.object_type FROM gv$session s JOIN gv$lock l ON s.sid = l.sid AND s.inst_id = l.inst_id JOIN dba_objects o ON l.id1 = o.object_id WHERE l.type IN ('TM', 'DL') AND s.username IS NOT NULL ORDER BY s.inst_id, s.sid; Includes lock type (TM, DL), lock mode, object info 📄 50_blocking_session_chains.sql Shows blocking and blocked session chains SET PAGESIZE 100 SET LINESIZE 200 COLUMN inst_id FORMAT 99 COLUMN sid FORMAT 99999 COLUMN serial# FORMAT 99999 COLUMN username FORMAT A15 COLUMN blocking_sid FORMAT 99999 COLUMN blocking_inst FORMAT 99 COLUMN wait_event FORMAT A40 COLUMN status FORMAT A10 PROMPT === BLOCKING SESSION CHAINS (GV$SESSION) === SELECT s.inst_id, s.sid, s.serial#, s.username, s.blocking_session AS blocking_sid, s.blocking_instance AS blocking_inst, s.event AS wait_event, CASE WHEN s.blocking_session IS NOT NULL THEN 'BLOCKED' ELSE 'OK' END AS status FROM gv$session s WHERE s.username IS NOT NULL AND s.blocking_session IS NOT NULL ORDER BY s.inst_id, s.sid; Includes instance, SID, username, blocking SID/INST, and event SET PAGESIZE 100 SET LINESIZE 200 COLUMN service_name FORMAT A25 COLUMN inst_id FORMAT 99 COLUMN session_type FORMAT A10 COLUMN active_count FORMAT 99999 COLUMN cpu_count FORMAT 99 COLUMN status FORMAT A10 PROMPT === ACTIVE SESSIONS PER INSTANCE (SCALED TO CPU CORES) - ORACLE 19C RAC === WITH session_stats AS ( SELECT inst_id, service_hash, session_type, COUNT(CASE WHEN session_state IN ('ON CPU','WAITING') THEN 1 END) AS active_count FROM gv$active_session_history WHERE sample_time > SYSDATE - &DAYS_AGO GROUP BY inst_id, service_hash, session_type ), cpu_cores AS ( SELECT instance_number AS inst_id, MAX(value) AS cpu_count FROM dba_hist_osstat WHERE stat_name = 'NUM_CPUS' GROUP BY instance_number ), services AS ( SELECT name_hash, name AS service_name FROM gv$services ) SELECT NVL(s.service_name, 'Unknown') AS service_name, ss.inst_id, ss.session_type, ss.active_count, c.cpu_count, CASE WHEN ss.active_count > c.cpu_count THEN 'CRITICAL' WHEN ss.active_count > c.cpu_count * 0.75 THEN 'WARNING' ELSE 'OK' END AS status FROM session_stats ss JOIN cpu_cores c ON ss.inst_id = c.inst_id LEFT JOIN services s ON ss.service_hash = s.name_hash ORDER BY ss.inst_id, service_name; SET PAGESIZE 100 SET LINESIZE 200 COLUMN originating_timestamp FORMAT A30 COLUMN message_text FORMAT A100 COLUMN inst_id FORMAT 99 COLUMN instance_name FORMAT A20 COLUMN status FORMAT A10 PROMPT === ORA- ERRORS FROM ALERT LOG (LAST 3 HOURS FOR ALL RAC INSTANCES) === WITH alert_logs AS ( SELECT originating_timestamp, message_text, inst_id FROM x$dbgalertext WHERE originating_timestamp > SYSDATE - 3/24 AND LOWER(message_text) LIKE '%ora-%' ), instance_info AS ( SELECT inst_id, instance_name FROM gv$instance ) SELECT TO_CHAR(a.originating_timestamp, 'YYYY-MM-DD HH24:MI:SS') AS originating_timestamp, a.inst_id, i.instance_name, a.message_text, CASE WHEN LOWER(a.message_text) LIKE '%ora-%' THEN 'CRITICAL' ELSE 'OK' END AS status FROM alert_logs a JOIN instance_info i ON a.inst_id = i.inst_id ORDER BY a.originating_timestamp DESC; 01_db_load.sql ✅ (done) SET PAGESIZE 100 SET LINESIZE 200 COLUMN begin_time FORMAT A20 COLUMN instance_name FORMAT A20 COLUMN cpu_count FORMAT 99 COLUMN db_time_mins FORMAT 999999.99 COLUMN aas FORMAT 999.99 COLUMN status FORMAT A10 PROMPT === DATABASE LOAD (AAS AUTO-SCALED BY CPU CORES PER INSTANCE) - ORACLE 19C RAC === WITH db_time_data AS ( SELECT s.snap_id, s.instance_number, s.begin_interval_time, s.end_interval_time, MAX(CASE WHEN tm.stat_name = 'DB time' THEN tm.value END) AS db_time FROM dba_hist_sys_time_model tm JOIN dba_hist_snapshot s ON tm.snap_id = s.snap_id AND tm.instance_number = s.instance_number WHERE s.begin_interval_time > SYSDATE - &DAYS_AGO AND tm.stat_name = 'DB time' GROUP BY s.snap_id, s.instance_number, s.begin_interval_time, s.end_interval_time ), cpu_cores AS ( SELECT instance_number, MAX(VALUE) AS cpu_count FROM dba_hist_osstat WHERE stat_name = 'NUM_CPUS' GROUP BY instance_number ), instance_names AS ( SELECT DISTINCT instance_number, instance_name FROM gv$instance ) SELECT TO_CHAR(d.begin_interval_time, 'YYYY-MM-DD HH24:MI') AS begin_time, i.instance_name, c.cpu_count, ROUND(d.db_time / 1000000 / 60, 2) AS db_time_mins, ROUND((d.db_time / 1000000 / 60) / ((CAST(d.end_interval_time AS DATE) - CAST(d.begin_interval_time AS DATE)) * 24 * 60), 2) AS aas, CASE WHEN ROUND((d.db_time / 1000000 / 60) / ((CAST(d.end_interval_time AS DATE) - CAST(d.begin_interval_time AS DATE)) * 24 * 60), 2) > c.cpu_count * 1.0 THEN 'CRITICAL' WHEN ROUND((d.db_time / 1000000 / 60) / ((CAST(d.end_interval_time AS DATE) - CAST(d.begin_interval_time AS DATE)) * 24 * 60), 2) > c.cpu_count * 0.75 THEN 'WARNING' ELSE 'OK' END AS status FROM db_time_data d JOIN cpu_cores c ON d.instance_number = c.instance_number LEFT JOIN instance_names i ON d.instance_number = i.instance_number ORDER BY d.begin_interval_time DESC, i.instance_name; 07_io_response_time.sql 09_rac_instance_skew.sql 22_ora_errors_last_3hr.sql 08_wait_events_window.sql 32_sga_pga_advisory.sql 42_realtime_aas.sql SET PAGESIZE 100 SET LINESIZE 200 COLUMN inst_id FORMAT 99 COLUMN cpu_count FORMAT 99 COLUMN aas FORMAT 999.99 COLUMN status FORMAT A10 PROMPT === REAL-TIME AAS (GV$ACTIVE_SESSION_HISTORY, LAST &MINUTES_AGO MINUTES) === WITH active_sessions AS ( SELECT inst_id, COUNT(*) / (&MINUTES_AGO * 60) AS aas FROM gv$active_session_history WHERE sample_time > SYSDATE - (&MINUTES_AGO / 1440) AND session_type = 'FOREGROUND' GROUP BY inst_id ), cpu_cores AS ( SELECT instance_number AS inst_id, MAX(value) AS cpu_count FROM dba_hist_osstat WHERE stat_name = 'NUM_CPUS' GROUP BY instance_number ) SELECT a.inst_id, c.cpu_count, ROUND(a.aas, 2) AS aas, CASE WHEN a.aas > c.cpu_count THEN 'CRITICAL' WHEN a.aas > c.cpu_count * 0.75 THEN 'WARNING' ELSE 'OK' END AS status FROM active_sessions a JOIN cpu_cores c ON a.inst_id = c.inst_id ORDER BY a.inst_id; SET PAGESIZE 100 SET LINESIZE 200 COLUMN begin_time FORMAT A20 COLUMN instance_name FORMAT A20 COLUMN cpu_count FORMAT 99 COLUMN db_time_mins FORMAT 999999.99 COLUMN aas FORMAT 999.99 COLUMN status FORMAT A10 PROMPT === DATABASE LOAD (AAS AUTO-SCALED BY CPU CORES PER INSTANCE) - ORACLE 19C RAC === WITH db_time_data AS ( SELECT s.snap_id, s.instance_number, s.begin_interval_time, s.end_interval_time, MAX(CASE WHEN tm.stat_name = 'DB time' THEN tm.value END) AS db_time FROM dba_hist_sys_time_model tm JOIN dba_hist_snapshot s ON tm.snap_id = s.snap_id AND tm.instance_number = s.instance_number WHERE s.begin_interval_time > SYSDATE - &DAYS_AGO AND tm.stat_name = 'DB time' GROUP BY s.snap_id, s.instance_number, s.begin_interval_time, s.end_interval_time ), cpu_cores AS ( SELECT instance_number, MAX(VALUE) AS cpu_count FROM dba_hist_osstat WHERE stat_name = 'NUM_CPUS' GROUP BY instance_number ), instance_names AS ( SELECT DISTINCT instance_number, instance_name FROM gv$instance ) SELECT TO_CHAR(d.begin_interval_time, 'YYYY-MM-DD HH24:MI') AS begin_time, i.instance_name, c.cpu_count, ROUND(d.db_time / 1000000 / 60, 2) AS db_time_mins, ROUND((d.db_time / 1000000 / 60) / ((d.end_interval_time - d.begin_interval_time) * 24 * 60), 2) AS aas, CASE WHEN ROUND((d.db_time / 1000000 / 60) / ((d.end_interval_time - d.begin_interval_time) * 24 * 60), 2) > c.cpu_count * 1.0 THEN 'CRITICAL' WHEN ROUND((d.db_time / 1000000 / 60) / ((d.end_interval_time - d.begin_interval_time) * 24 * 60), 2) > c.cpu_count * 0.75 THEN 'WARNING' ELSE 'OK' END AS status FROM db_time_data d JOIN cpu_cores c ON d.instance_number = c.instance_number LEFT JOIN instance_names i ON d.instance_number = i.instance_number ORDER BY d.begin_interval_time DESC, i.instance_name; SET PAGESIZE 100 SET LINESIZE 200 COLUMN sample_time FORMAT A20 COLUMN instance FORMAT 99 COLUMN service_name FORMAT A20 COLUMN session_type FORMAT A15 COLUMN status FORMAT A10 COLUMN active_count FORMAT 99999 COLUMN inactive_count FORMAT 99999 PROMPT === ACTIVE / INACTIVE SESSIONS BY SERVICE NAME (LAST &DAYS_AGO DAYS) - ORACLE 19C RAC === SELECT service_hash, service_name, instance_number AS instance, session_type, COUNT(CASE WHEN session_state = 'ON CPU' OR session_state = 'WAITING' THEN 1 END) AS active_count, COUNT(CASE WHEN session_state = 'CACHED' OR session_state = 'INACTIVE' THEN 1 END) AS inactive_count, CASE WHEN COUNT(CASE WHEN session_state = 'ON CPU' OR session_state = 'WAITING' THEN 1 END) > 80 THEN 'CRITICAL' WHEN COUNT(CASE WHEN session_state = 'ON CPU' OR session_state = 'WAITING' THEN 1 END) > 20 THEN 'WARNING' ELSE 'OK' END AS status FROM gv$active_session_history WHERE sample_time > SYSDATE - &DAYS_AGO GROUP BY service_hash, service_name, instance_number, session_type ORDER BY instance, service_name; SET PAGESIZE 100 SET LINESIZE 200 COLUMN service_name FORMAT A25 COLUMN inst_id FORMAT 99 COLUMN session_type FORMAT A10 COLUMN active_count FORMAT 99999 COLUMN cpu_count FORMAT 99 COLUMN status FORMAT A10 PROMPT === ACTIVE SESSIONS PER INSTANCE (SCALED TO CPU CORES) - ORACLE 19C RAC === WITH session_stats AS ( SELECT inst_id, service_hash, session_type, COUNT(CASE WHEN session_state IN ('ON CPU','WAITING') THEN 1 END) AS active_count FROM gv$active_session_history WHERE sample_time > SYSDATE - &DAYS_AGO GROUP BY inst_id, service_hash, session_type ), cpu_cores AS ( SELECT instance_number AS inst_id, MAX(value) AS cpu_count FROM dba_hist_osstat WHERE stat_name = 'NUM_CPUS' GROUP BY instance_number ), services AS ( SELECT name_hash, name AS service_name FROM gv$services ) SELECT NVL(s.service_name, 'Unknown') AS service_name, ss.inst_id, ss.session_type, ss.active_count, c.cpu_count, CASE WHEN ss.active_count > c.cpu_count THEN 'CRITICAL' WHEN ss.active_count > c.cpu_count * 0.75 THEN 'WARNING' ELSE 'OK' END AS status FROM session_stats ss JOIN cpu_cores c ON ss.inst_id = c.inst_id LEFT JOIN services s ON ss.service_hash = s.name_hash ORDER BY ss.inst_id, service_name; 9 SET PAGESIZE 100 SET LINESIZE 200 COLUMN begin_time FORMAT A20 COLUMN instance_name FORMAT A20 COLUMN db_time_per_cpu FORMAT 999999.99 COLUMN io_mb FORMAT 999999.99 COLUMN status FORMAT A10 PROMPT === INSTANCE SKEW (PER-CPU LOAD & I/O) - ORACLE 19C RAC === WITH workload AS ( SELECT s.snap_id, s.begin_interval_time, s.instance_number, MAX(CASE WHEN tm.stat_name = 'DB time' THEN tm.value END)/1000000 AS db_time_secs FROM dba_hist_sys_time_model tm JOIN dba_hist_snapshot s ON tm.snap_id = s.snap_id AND tm.instance_number = s.instance_number WHERE s.begin_interval_time > SYSDATE - &DAYS_AGO AND tm.stat_name = 'DB time' GROUP BY s.snap_id, s.begin_interval_time, s.instance_number ), io_stats AS ( SELECT snap_id, instance_number, SUM(CASE WHEN stat_name IN ('physical read bytes', 'physical write bytes') THEN value ELSE 0 END)/1024/1024 AS io_mb FROM dba_hist_sysstat WHERE stat_name IN ('physical read bytes', 'physical write bytes') GROUP BY snap_id, instance_number ), cpu_cores AS ( SELECT instance_number, MAX(value) AS cpu_count FROM dba_hist_osstat WHERE stat_name = 'NUM_CPUS' GROUP BY instance_number ), instance_names AS ( SELECT DISTINCT instance_number, instance_name FROM gv$instance ), combined AS ( SELECT w.snap_id, w.begin_interval_time, w.instance_number, w.db_time_secs, i.io_mb, c.cpu_count, ROUND(w.db_time_secs / c.cpu_count, 2) AS db_time_per_cpu FROM workload w JOIN cpu_cores c ON w.instance_number = c.instance_number LEFT JOIN io_stats i ON w.snap_id = i.snap_id AND w.instance_number = i.instance_number ) SELECT TO_CHAR(c.begin_interval_time, 'YYYY-MM-DD HH24:MI') AS begin_time, n.instance_name, c.db_time_per_cpu, ROUND(c.io_mb, 2) AS io_mb, CASE WHEN c.db_time_per_cpu > (SELECT AVG(db_time_per_cpu) * 1.5 FROM combined WHERE begin_interval_time = c.begin_interval_time) THEN 'CRITICAL' WHEN c.db_time_per_cpu > (SELECT AVG(db_time_per_cpu) * 1.2 FROM combined WHERE begin_interval_time = c.begin_interval_time) THEN 'WARNING' WHEN c.io_mb > (SELECT AVG(io_mb) * 1.5 FROM combined WHERE begin_interval_time = c.begin_interval_time) THEN 'CRITICAL' WHEN c.io_mb > (SELECT AVG(io_mb) * 1.2 FROM combined WHERE begin_interval_time = c.begin_interval_time) THEN 'WARNING' ELSE 'OK' END AS status FROM combined c LEFT JOIN instance_names n ON c.instance_number = n.instance_number ORDER BY c.begin_interval_time DESC, n.instance_name; 44_rac_gc_waits.sql – Global cache contention (top events) 45_rac_gc_waits_by_instance.sql – GC wait breakdown per node 46_rac_blocking_ges.sql – GES blocking sessions (RAC locks) 47_rac_interconnect_stats.sql – Interconnect activity (GC blocks) 48_rac_global_enqueue_contention.sql – Enqueue waits via GES 48 SET PAGESIZE 100 SET LINESIZE 200 COLUMN event FORMAT A40 COLUMN samples FORMAT 99999 COLUMN status FORMAT A10 PROMPT === GLOBAL ENQUEUE CONTENTION (GV$ACTIVE_SESSION_HISTORY) === SELECT event, COUNT(*) AS samples, CASE WHEN COUNT(*) > 50 THEN 'CRITICAL' ELSE 'OK' END AS status FROM gv$active_session_history WHERE event LIKE 'ges%' AND sample_time > SYSDATE - (&HOURS_AGO / 24) GROUP BY event ORDER BY samples DESC; 47 SET PAGESIZE 100 SET LINESIZE 200 COLUMN inst_id FORMAT 99 COLUMN name FORMAT A40 COLUMN mb FORMAT 999999.99 COLUMN status FORMAT A10 PROMPT === HIGH INTERCONNECT ACTIVITY (GV$SYSSTAT) === SELECT inst_id, name, ROUND(value / 1024 / 1024, 2) AS mb, CASE WHEN value > 500000000 THEN 'CRITICAL' ELSE 'OK' END AS status FROM gv$sysstat WHERE name IN ( 'gc current blocks received', 'gc cr blocks received', 'gc current blocks served', 'gc cr blocks served' ) ORDER BY inst_id, name; 46 SET PAGESIZE 100 SET LINESIZE 200 COLUMN blocking_session FORMAT 99999 COLUMN blocking_inst_id FORMAT 99 COLUMN blocks FORMAT 9999 COLUMN first_seen FORMAT A20 COLUMN last_seen FORMAT A20 COLUMN status FORMAT A10 PROMPT === GES BLOCKING EVENTS (GV$ACTIVE_SESSION_HISTORY) === SELECT blocking_session, blocking_inst_id, COUNT(*) AS blocks, TO_CHAR(MIN(sample_time), 'YYYY-MM-DD HH24:MI') AS first_seen, TO_CHAR(MAX(sample_time), 'YYYY-MM-DD HH24:MI') AS last_seen, CASE WHEN COUNT(*) > 20 THEN 'CRITICAL' ELSE 'OK' END AS status FROM gv$active_session_history WHERE blocking_session IS NOT NULL AND sample_time > SYSDATE - (&HOURS_AGO / 24) GROUP BY blocking_session, blocking_inst_id ORDER BY blocks DESC; 45 SET PAGESIZE 100 SET LINESIZE 200 COLUMN inst_id FORMAT 99 COLUMN event FORMAT A40 COLUMN count FORMAT 99999 COLUMN status FORMAT A10 PROMPT === GC WAITS BY INSTANCE (GV$ACTIVE_SESSION_HISTORY) === SELECT inst_id, event, COUNT(*) AS count, CASE WHEN COUNT(*) > 500 THEN 'CRITICAL' WHEN COUNT(*) > 200 THEN 'WARNING' ELSE 'OK' END AS status FROM gv$active_session_history WHERE event LIKE 'gc%' AND sample_time > SYSDATE - (&HOURS_AGO / 24) GROUP BY inst_id, event ORDER BY inst_id, count DESC;45 44 SET PAGESIZE 100 SET LINESIZE 200 COLUMN event FORMAT A40 COLUMN samples FORMAT 99999 COLUMN pct FORMAT 999.99 COLUMN status FORMAT A10 PROMPT === TOP GLOBAL CACHE WAITS (GV$ACTIVE_SESSION_HISTORY) === SELECT event, COUNT(*) AS samples, ROUND(COUNT(*) * 100 / SUM(COUNT(*)) OVER (), 2) AS pct, CASE WHEN event LIKE 'gc%' AND COUNT(*) > 100 THEN 'CRITICAL' ELSE 'OK' END AS status FROM gv$active_session_history WHERE event LIKE 'gc%' AND sample_time > SYSDATE - (&HOURS_AGO / 24) GROUP BY event ORDER BY samples DESC FETCH FIRST 10 ROWS ONLY; 43 SET PAGESIZE 100 SET LINESIZE 200 COLUMN inst_id FORMAT 99 COLUMN cpu_busy_secs FORMAT 999999.99 COLUMN total_cpu_secs FORMAT 999999.99 COLUMN cpu_util_pct FORMAT 999.99 COLUMN status FORMAT A10 PROMPT === REAL-TIME CPU UTILIZATION (GV$OSSTAT) - ORACLE 19C RAC === WITH os_stat AS ( SELECT inst_id, MAX(CASE WHEN stat_name = 'BUSY_TIME' THEN value END) AS busy_time, MAX(CASE WHEN stat_name = 'IDLE_TIME' THEN value END) AS idle_time FROM gv$osstat WHERE stat_name IN ('BUSY_TIME', 'IDLE_TIME') GROUP BY inst_id ) SELECT inst_id, ROUND(busy_time / 100, 2) AS cpu_busy_secs, ROUND((busy_time + idle_time) / 100, 2) AS total_cpu_secs, ROUND((busy_time / (busy_time + idle_time)) * 100, 2) AS cpu_util_pct, CASE WHEN (busy_time / (busy_time + idle_time)) * 100 > 90 THEN 'CRITICAL' WHEN (busy_time / (busy_time + idle_time)) * 100 > 75 THEN 'WARNING' ELSE 'OK' END AS status FROM os_stat ORDER BY inst_id; 42 SET PAGESIZE 100 SET LINESIZE 200 COLUMN inst_id FORMAT 99 COLUMN cpu_count FORMAT 99 COLUMN aas FORMAT 999.99 COLUMN status FORMAT A10 PROMPT === REAL-TIME AAS (GV$ACTIVE_SESSION_HISTORY, LAST &MINUTES_AGO MINUTES) === WITH active_sessions AS ( SELECT inst_id, COUNT(*) / (&MINUTES_AGO * 60) AS aas FROM gv$active_session_history WHERE sample_time > SYSDATE - (&MINUTES_AGO / 1440) AND session_type = 'FOREGROUND' GROUP BY inst_id ), cpu_cores AS ( SELECT instance_number AS inst_id, MAX(value) AS cpu_count FROM dba_hist_osstat WHERE stat_name = 'NUM_CPUS' GROUP BY instance_number ) SELECT a.inst_id, c.cpu_count, ROUND(a.aas, 2) AS aas, CASE WHEN a.aas > c.cpu_count THEN 'CRITICAL' WHEN a.aas > c.cpu_count * 0.75 THEN 'WARNING' ELSE 'OK' END AS status FROM active_sessions a JOIN cpu_cores c ON a.inst_id = c.inst_id ORDER BY a.inst_id; 22 SET PAGESIZE 100 SET LINESIZE 200 COLUMN originating_timestamp FORMAT A30 COLUMN message_text FORMAT A100 COLUMN inst_id FORMAT 99 COLUMN status FORMAT A10 PROMPT === ORA- ERRORS IN ALERT LOG (LAST 3 HOURS) - ORACLE 19C RAC === SELECT inst_id, TO_CHAR(CAST(originating_timestamp AS DATE), 'YYYY-MM-DD HH24:MI:SS') AS originating_timestamp, message_text, CASE WHEN LOWER(message_text) LIKE '%ora-%' THEN 'CRITICAL' ELSE 'OK' END AS status FROM gv$diag_alert_ext WHERE CAST(originating_timestamp AS DATE) > SYSDATE - 3/24 AND LOWER(message_text) LIKE '%ora-%' ORDER BY originating_timestamp DESC; 9 SET PAGESIZE 100 SET LINESIZE 200 COLUMN begin_time FORMAT A20 COLUMN instance_name FORMAT A20 COLUMN db_time_per_cpu FORMAT 999999.99 COLUMN io_mb FORMAT 999999.99 COLUMN status FORMAT A10 PROMPT === INSTANCE SKEW (PER-CPU LOAD & I/O) - ORACLE 19C RAC === WITH workload AS ( SELECT s.snap_id, s.begin_interval_time, s.instance_number, MAX(CASE WHEN tm.stat_name = 'DB time' THEN tm.value END)/1000000 AS db_time_secs FROM dba_hist_sys_time_model tm JOIN dba_hist_snapshot s ON tm.snap_id = s.snap_id AND tm.instance_number = s.instance_number WHERE s.begin_interval_time > SYSDATE - &DAYS_AGO AND tm.stat_name = 'DB time' GROUP BY s.snap_id, s.begin_interval_time, s.instance_number ), io_stats AS ( SELECT snap_id, instance_number, SUM(CASE WHEN stat_name IN ('physical read bytes', 'physical write bytes') THEN value ELSE 0 END)/1024/1024 AS io_mb FROM dba_hist_sysstat WHERE stat_name IN ('physical read bytes', 'physical write bytes') GROUP BY snap_id, instance_number ), cpu_cores AS ( SELECT instance_number, MAX(value) AS cpu_count FROM dba_hist_osstat WHERE stat_name = 'NUM_CPUS' GROUP BY instance_number ), instance_names AS ( SELECT DISTINCT instance_number, instance_name FROM gv$instance ), combined AS ( SELECT w.snap_id, w.begin_interval_time, w.instance_number, w.db_time_secs, i.io_mb, c.cpu_count, ROUND(w.db_time_secs / c.cpu_count, 2) AS db_time_per_cpu FROM workload w JOIN cpu_cores c ON w.instance_number = c.instance_number LEFT JOIN io_stats i ON w.snap_id = i.snap_id AND w.instance_number = i.instance_number ) SELECT TO_CHAR(c.begin_interval_time, 'YYYY-MM-DD HH24:MI') AS begin_time, n.instance_name, c.db_time_per_cpu, ROUND(c.io_mb, 2) AS io_mb, CASE WHEN c.db_time_per_cpu > (SELECT AVG(db_time_per_cpu) * 1.5 FROM combined WHERE begin_interval_time = c.begin_interval_time) THEN 'CRITICAL' WHEN c.db_time_per_cpu > (SELECT AVG(db_time_per_cpu) * 1.2 FROM combined WHERE begin_interval_time = c.begin_interval_time) THEN 'WARNING' WHEN c.io_mb > (SELECT AVG(io_mb) * 1.5 FROM combined WHERE begin_interval_time = c.begin_interval_time) THEN 'CRITICAL' WHEN c.io_mb > (SELECT AVG(io_mb) * 1.2 FROM combined WHERE begin_interval_time = c.begin_interval_time) THEN 'WARNING' ELSE 'OK' END AS status FROM combined c LEFT JOIN instance_names n ON c.instance_number = n.instance_number ORDER BY c.begin_interval_time DESC, n.instance_name; 8 SET PAGESIZE 100 SET LINESIZE 200 COLUMN sample_time FORMAT A20 COLUMN event FORMAT A40 COLUMN wait_class FORMAT A20 COLUMN instance FORMAT 99 COLUMN count FORMAT 99999 COLUMN status FORMAT A10 PROMPT === TOP WAIT EVENTS (LAST &MINUTES_AGO MINUTES AND &HOURS_AGO HOURS) - ORACLE 19C RAC === -- Top events in the last &MINUTES_AGO minutes PROMPT PROMPT --- Wait Events (Last &MINUTES_AGO Minutes) --- SELECT event, wait_class, instance_number AS instance, COUNT(*) AS count, CASE WHEN COUNT(*) > 100 THEN 'CRITICAL' WHEN COUNT(*) > 50 THEN 'WARNING' ELSE 'OK' END AS status FROM gv$active_session_history WHERE CAST(sample_time AS DATE) >= SYSDATE - (&MINUTES_AGO / (24 * 60)) GROUP BY event, wait_class, instance_number ORDER BY count DESC FETCH FIRST 10 ROWS ONLY; -- Top events in the last &HOURS_AGO hours PROMPT PROMPT --- Wait Events (Last &HOURS_AGO Hours) --- SELECT event, wait_class, instance_number AS instance, COUNT(*) AS count, CASE WHEN COUNT(*) > 500 THEN 'CRITICAL' WHEN COUNT(*) > 200 THEN 'WARNING' ELSE 'OK' END AS status FROM gv$active_session_history WHERE CAST(sample_time AS DATE) >= SYSDATE - (&HOURS_AGO / 24) GROUP BY event, wait_class, instance_number ORDER BY count DESC FETCH FIRST 10 ROWS ONLY; SET PAGESIZE 100 SET LINESIZE 200 COLUMN begin_time FORMAT A20 COLUMN instance_name FORMAT A20 COLUMN avg_latency FORMAT 999.99 COLUMN io_requests FORMAT 9999999 COLUMN status FORMAT A10 PROMPT === IO RESPONSE TIME (AVG SYNC SINGLE-BLOCK READ LATENCY) - ORACLE 19C === WITH io_latency AS ( SELECT s.snap_id, CAST(s.begin_interval_time AS DATE) AS begin_interval_time, h.instance_number, MAX(CASE WHEN h.metric_name = 'Average Synchronous Single-Block Read Latency' THEN h.value END) AS avg_latency, MAX(CASE WHEN h.metric_name = 'Physical Read Total IO Requests Per Sec' THEN h.value END) + MAX(CASE WHEN h.metric_name = 'Physical Write Total IO Requests Per Sec' THEN h.value END) AS io_requests FROM dba_hist_sysmetric_history h JOIN dba_hist_snapshot s ON h.snap_id = s.snap_id AND h.instance_number = s.instance_number WHERE h.metric_name IN ( 'Average Synchronous Single-Block Read Latency', 'Physical Read Total IO Requests Per Sec', 'Physical Write Total IO Requests Per Sec' ) AND s.begin_interval_time >= SYSDATE - &DAYS_AGO GROUP BY s.snap_id, s.begin_interval_time, h.instance_number ), instances AS ( SELECT DISTINCT instance_number, instance_name FROM gv$instance ) SELECT TO_CHAR(i.begin_interval_time, 'YYYY-MM-DD HH24:MI') AS begin_time, n.instance_name, ROUND(i.avg_latency, 2) AS avg_latency, ROUND(i.io_requests, 2) AS io_requests, CASE WHEN i.avg_latency > 20 THEN 'CRITICAL' WHEN i.avg_latency > 10 THEN 'WARNING' ELSE 'OK' END AS status FROM io_latency i LEFT JOIN instances n ON i.instance_number = n.instance_number ORDER BY i.begin_interval_time DESC, n.instance_name; ## SET PAGESIZE 100 SET LINESIZE 200 COLUMN service_name FORMAT A25 COLUMN inst_id FORMAT 99 COLUMN session_type FORMAT A10 COLUMN active_count FORMAT 99999 COLUMN cpu_count FORMAT 99 COLUMN status FORMAT A10 PROMPT === ACTIVE SESSIONS PER INSTANCE (SCALED TO CPU CORES) - ORACLE 19C RAC === WITH session_stats AS ( SELECT inst_id, service_hash, session_type, COUNT(CASE WHEN session_state IN ('ON CPU','WAITING') THEN 1 END) AS active_count FROM gv$active_session_history WHERE sample_time > SYSDATE - &DAYS_AGO GROUP BY inst_id, service_hash, session_type ), cpu_cores AS ( SELECT instance_number AS inst_id, MAX(value) AS cpu_count FROM dba_hist_osstat WHERE stat_name = 'NUM_CPUS' GROUP BY instance_number ), services AS ( SELECT name_hash, name AS service_name FROM gv$services ) SELECT NVL(s.service_name, 'Unknown') AS service_name, ss.inst_id, ss.session_type, ss.active_count, c.cpu_count, CASE WHEN ss.active_count > c.cpu_count THEN 'CRITICAL' WHEN ss.active_count > c.cpu_count * 0.75 THEN 'WARNING' ELSE 'OK' END AS status FROM session_stats ss JOIN cpu_cores c ON ss.inst_id = c.inst_id LEFT JOIN services s ON ss.service_hash = s.name_hash ORDER BY ss.inst_id, service_name; ##### 44_rac_gc_waits.sql – Global cache contention (top events) 45_rac_gc_waits_by_instance.sql – GC wait breakdown per node 46_rac_blocking_ges.sql – GES blocking sessions (RAC locks) 47_rac_interconnect_stats.sql – Interconnect activity (GC blocks) 48_rac_global_enqueue_contention.sql – Enqueue waits via GES DAYS_AGO=1 HOURS_AGO=1 MINUTES_AGO=5 is_exadata() { echo "Checking for Exadata platform..." if sqlplus -s "$DB_CONNECT_STRING" < "$tmp_output" SET LINESIZE 200 SET PAGESIZE 100 SET FEEDBACK OFF SET VERIFY OFF DEFINE DAYS_AGO=$DAYS_AGO DEFINE HOURS_AGO=$HOURS_AGO DEFINE MINUTES_AGO=$MINUTES_AGO @$sql_file EXIT EOF if grep -q 'CRITICAL' "$tmp_output"; then section_status="CRITICAL" elif grep -q 'WARNING' "$tmp_output"; then section_status="WARNING" else section_status="OK" fi echo "

$title - Status: $section_status

" >> "$HTML_REPORT"

    while IFS= read -r line; do
        if echo "$line" | grep -q 'CRITICAL'; then
            line=$(echo "$line" | sed 's/CRITICAL/CRITICAL<\/b><\/span>/g')
        elif echo "$line" | grep -q 'WARNING'; then
            line=$(echo "$line" | sed 's/WARNING/WARNING<\/b><\/span>/g')
        elif echo "$line" | grep -q 'OK'; then
            line=$(echo "$line" | sed 's/OK/OK<\/b><\/span>/g')
        fi
        echo "$line" >> "$HTML_REPORT"
    done < "$tmp_output"

    rm -f "$tmp_output"
    echo "
" >> "$HTML_REPORT" } echo "Oracle RAC Health Check" > "$HTML_REPORT" echo "

Oracle RAC Health Check Report

" >> "$HTML_REPORT" # Example modules run_sql_file_report "DB Load (AAS)" "$SQL_DIR/01_db_load.sql" "$DB_CONNECT_STRING" run_sql_file_report "Long Running Sessions" "$SQL_DIR/06_long_running_sessions.sql" "$DB_CONNECT_STRING" run_sql_file_report "IO Response Time" "$SQL_DIR/07_io_response_time.sql" "$DB_CONNECT_STRING" if is_exadata; then run_sql_file_report "Exadata Offload Efficiency" "$SQL_DIR/35_exadata_offload_efficiency.sql" "$DB_CONNECT_STRING" run_sql_file_report "Exadata Cell Interconnect Waits" "$SQL_DIR/36_exadata_cell_interconnect_waits.sql" "$DB_CONNECT_STRING" run_sql_file_report "Exadata Flash Cache Stats" "$SQL_DIR/37_exadata_flashcache_stats.sql" "$DB_CONNECT_STRING" run_sql_file_report "Exadata Smart Scan Usage" "$SQL_DIR/38_exadata_smart_scan_usage.sql" "$DB_CONNECT_STRING" run_sql_file_report "ASM Diskgroup Status" "$SQL_DIR/39_exadata_asm_diskgroup_status.sql" "$DB_CONNECT_STRING" run_sql_file_report "Exadata Wait Class Usage" "$SQL_DIR/40_exadata_wait_class_usage.sql" "$DB_CONNECT_STRING" run_sql_file_report "IORM Plan Check" "$SQL_DIR/41_exadata_iorm_plan.sql" "$DB_CONNECT_STRING" fi echo "" >> "$HTML_REPORT" SET PAGESIZE 100 SET LINESIZE 200 COLUMN begin_time FORMAT A20 COLUMN instance_name FORMAT A20 COLUMN cpu_count FORMAT 99 COLUMN db_time_mins FORMAT 999999.99 COLUMN aas FORMAT 999.99 COLUMN status FORMAT A10 PROMPT === DATABASE LOAD (AAS AUTO-SCALED BY CPU CORES PER INSTANCE) - ORACLE 19C RAC === WITH db_time_data AS ( SELECT s.snap_id, s.instance_number, s.begin_interval_time, s.end_interval_time, MAX(CASE WHEN tm.stat_name = 'DB time' THEN tm.value END) AS db_time FROM dba_hist_sys_time_model tm JOIN dba_hist_snapshot s ON tm.snap_id = s.snap_id AND tm.instance_number = s.instance_number WHERE s.begin_interval_time > SYSDATE - &DAYS_AGO AND tm.stat_name = 'DB time' GROUP BY s.snap_id, s.instance_number, s.begin_interval_time, s.end_interval_time ), cpu_cores AS ( SELECT instance_number, MAX(VALUE) AS cpu_count FROM dba_hist_osstat WHERE stat_name = 'NUM_CPUS' GROUP BY instance_number ), instance_names AS ( SELECT DISTINCT instance_number, instance_name FROM gv$instance ) SELECT TO_CHAR(d.begin_interval_time, 'YYYY-MM-DD HH24:MI') AS begin_time, i.instance_name, c.cpu_count, ROUND(d.db_time / 1000000 / 60, 2) AS db_time_mins, ROUND((d.db_time / 1000000 / 60) / ((CAST(d.end_interval_time AS DATE) - CAST(d.begin_interval_time AS DATE)) * 24 * 60), 2) AS aas, CASE WHEN ROUND((d.db_time / 1000000 / 60) / ((CAST(d.end_interval_time AS DATE) - CAST(d.begin_interval_time AS DATE)) * 24 * 60), 2) > c.cpu_count * 1.0 THEN 'CRITICAL' WHEN ROUND((d.db_time / 1000000 / 60) / ((CAST(d.end_interval_time AS DATE) - CAST(d.begin_interval_time AS DATE)) * 24 * 60), 2) > c.cpu_count * 0.75 THEN 'WARNING' ELSE 'OK' END AS status FROM db_time_data d JOIN cpu_cores c ON d.instance_number = c.instance_number LEFT JOIN instance_names i ON d.instance_number = i.instance_number ORDER BY d.begin_interval_time DESC, i.instance_name; 2025-05-05 06:31 AM 1,609 01_db_load.sql 2025-05-05 06:44 AM 872 06_long_running_sessions.sql 2025-05-05 06:35 AM 1,970 07_io_response_time.sql 2025-05-05 07:02 AM 1,335 08_wait_events_window.sql 2025-05-05 07:05 AM 1,699 09_rac_instance_skew.sql 2025-05-05 07:02 AM 562 22_ora_errors_last_3hr.sql 2025-05-05 06:52 AM 777 30_downgraded_parallel_sessions.sql 2025-05-05 06:28 AM 1,174 32_sga_pga_advisory.sql 2025-05-05 06:14 AM 560 33_top_sql_io.sql SET PAGESIZE 100 SET LINESIZE 200 COLUMN sql_id FORMAT A15 COLUMN executions FORMAT 9999999 COLUMN disk_reads FORMAT 9999999 COLUMN avg_io FORMAT 999999.99 COLUMN module FORMAT A20 PROMPT === TOP 5 SQLs BY DISK READS === SELECT * FROM ( SELECT sql_id, executions_delta AS executions, disk_reads_delta AS disk_reads, ROUND(disk_reads_delta / NULLIF(executions_delta, 0), 2) AS avg_io, module FROM dba_hist_sqlstat WHERE executions_delta > 0 ORDER BY disk_reads_delta DESC ) WHERE ROWNUM <= 5; 2025-05-05 06:13 AM 1,018 33_top_sql_io_cpu.sql SET PAGESIZE 100 SET LINESIZE 200 COLUMN sql_id FORMAT A15 COLUMN executions FORMAT 9999999 COLUMN cpu_time FORMAT 9999999.99 COLUMN disk_reads FORMAT 9999999 COLUMN module FORMAT A20 COLUMN avg_cpu FORMAT 999999.99 COLUMN avg_io FORMAT 999999.99 PROMPT === TOP 5 SQLs BY DISK READS === SELECT * FROM ( SELECT sql_id, executions_delta AS executions, disk_reads_delta AS disk_reads, ROUND(disk_reads_delta / NULLIF(executions_delta,0), 2) AS avg_io, module FROM dba_hist_sqlstat WHERE executions_delta > 0 ORDER BY disk_reads_delta DESC ) WHERE ROWNUM <= 5; PROMPT PROMPT === TOP 5 SQLs BY CPU TIME === SELECT * FROM ( SELECT sql_id, executions_delta AS executions, cpu_time_delta/1000000 AS cpu_time, ROUND((cpu_time_delta/1000000)/NULLIF(executions_delta,0), 2) AS avg_cpu, module FROM dba_hist_sqlstat WHERE executions_delta > 0 ORDER BY cpu_time_delta DESC ) WHERE ROWNUM <= 5; 2025-05-05 06:14 AM 569 34_top_sql_cpu.sql SET PAGESIZE 100 SET LINESIZE 200 COLUMN sql_id FORMAT A15 COLUMN executions FORMAT 9999999 COLUMN cpu_time FORMAT 9999999.99 COLUMN avg_cpu FORMAT 999999.99 COLUMN module FORMAT A20 PROMPT === TOP 5 SQLs BY CPU TIME === SELECT * FROM ( SELECT sql_id, executions_delta AS executions, cpu_time_delta/1000000 AS cpu_time, ROUND((cpu_time_delta/1000000)/NULLIF(executions_delta, 0), 2) AS avg_cpu, module FROM dba_hist_sqlstat WHERE executions_delta > 0 ORDER BY cpu_time_delta DESC ) WHERE ROWNUM <= 5; SET PAGESIZE 100 SET LINESIZE 200 COLUMN target_mb FORMAT 99999 COLUMN est_extra_rw_mb FORMAT 99999.99 COLUMN est_cache_hit FORMAT 999.99 COLUMN advice FORMAT A10 PROMPT === PGA TARGET ADVICE (Oracle 19c) === SELECT ROUND(pga_target_for_estimate / 1024 / 1024) AS target_mb, ROUND(estd_extra_bytes_rw / 1024 / 1024, 2) AS est_extra_rw_mb, estd_pga_cache_hit_percentage AS est_cache_hit, CASE WHEN estd_pga_cache_hit_percentage >= 99 THEN 'OK' WHEN estd_pga_cache_hit_percentage >= 90 THEN 'WARNING' ELSE 'CRITICAL' END AS advice FROM v$pga_target_advice WHERE estd_pga_cache_hit_percentage IS NOT NULL ORDER BY target_mb; PROMPT PROMPT === SGA TARGET ADVICE (Oracle 19c) === SELECT ROUND(sga_size / 1024) AS target_mb, estd_db_time / 100 AS est_db_time_seconds, estd_physical_reads, CASE WHEN estd_db_time <= MIN(estd_db_time) OVER () THEN 'OK' WHEN estd_db_time <= MIN(estd_db_time) OVER () * 1.1 THEN 'WARNING' ELSE 'CRITICAL' END AS advice FROM v$sga_target_advice WHERE sga_size_factor BETWEEN 0.5 AND 2 ORDER BY sga_size; ### SET PAGESIZE 100 SET LINESIZE 200 COLUMN inst_id FORMAT 99 COLUMN service_name FORMAT A20 COLUMN session_type FORMAT A15 COLUMN active_count FORMAT 99999 COLUMN inactive_count FORMAT 99999 COLUMN status FORMAT A10 PROMPT === ACTIVE / INACTIVE SESSIONS BY SERVICE NAME (LAST &DAYS_AGO DAYS) - ORACLE 19C RAC === SELECT NVL(s.name, 'Unknown') AS service_name, a.inst_id, a.session_type, COUNT(CASE WHEN a.session_state IN ('ON CPU', 'WAITING') THEN 1 END) AS active_count, COUNT(CASE WHEN a.session_state IN ('CACHED', 'INACTIVE') THEN 1 END) AS inactive_count, CASE WHEN COUNT(CASE WHEN a.session_state IN ('ON CPU', 'WAITING') THEN 1 END) > 80 THEN 'CRITICAL' WHEN COUNT(CASE WHEN a.session_state IN ('ON CPU', 'WAITING') THEN 1 END) > 20 THEN 'WARNING' ELSE 'OK' END AS status FROM gv$active_session_history a LEFT JOIN gv$services s ON a.service_hash = s.name_hash WHERE a.sample_time > SYSDATE - &DAYS_AGO GROUP BY NVL(s.name, 'Unknown'), a.inst_id, a.session_type ORDER BY a.inst_id, service_name; is_exadata() { echo "Checking for Exadata platform..." if sqlplus -s "$DB_CONNECT_STRING" < 1000000 THEN 'WARNING' ELSE 'OK' END AS status FROM v$system_event WHERE event LIKE 'cell%' ORDER BY time_waited_micro DESC FETCH FIRST 10 ROWS ONLY; ## PROMPT === FLASH CACHE STATISTICS (EXADATA) === SELECT name, value FROM v$sysstat WHERE name LIKE '%flash cache%' ORDER BY name; ## PROMPT === SMART SCAN USAGE CHECK (RECENT SQLs) === SELECT sql_id, offload_eligibility, offload_returned_bytes/1024/1024 AS returned_mb, offload_eligible_bytes/1024/1024 AS eligible_mb, CASE WHEN offload_eligibility = 'NONE' THEN 'WARNING' ELSE 'OK' END AS status FROM v$sql_monitor WHERE offload_eligibility IS NOT NULL AND last_refresh_time > SYSDATE - 1/24 ORDER BY last_refresh_time DESC FETCH FIRST 10 ROWS ONLY; ## PROMPT === ASM DISKGROUP SPACE & STATE === SELECT name, total_mb, free_mb, state, ROUND((free_mb/total_mb)*100, 2) AS pct_free, CASE WHEN state != 'MOUNTED' THEN 'CRITICAL' WHEN (free_mb/total_mb)*100 < 10 THEN 'WARNING' ELSE 'OK' END AS status FROM v$asm_diskgroup; PROMPT === EXADATA-SPECIFIC WAITS (GV$SESSION) === SELECT inst_id, wait_class, COUNT(*) AS count FROM gv$session WHERE wait_class IN ('Exadata', 'User I/O') GROUP BY inst_id, wait_class; ## PROMPT === IORM PLAN CHECK === SELECT plan_name, active, objective, status FROM v$cell_iorm_plan WHERE active = 'YES'; ## SET PAGESIZE 100 SET LINESIZE 200 COLUMN begin_time FORMAT A20 COLUMN instance_name FORMAT A20 COLUMN db_time_secs FORMAT 999999.99 COLUMN cpu_time_secs FORMAT 999999.99 COLUMN io_mb FORMAT 999999.99 COLUMN skew_status FORMAT A10 PROMPT === RAC INSTANCE SKEW ANALYSIS (DB TIME, CPU TIME, I/O) - ORACLE 19C === WITH workload AS ( SELECT s.snap_id, s.begin_interval_time, s.instance_number, MAX(CASE WHEN tm.stat_name = 'DB time' THEN tm.value END)/1000000 AS db_time_secs, MAX(CASE WHEN tm.stat_name = 'DB CPU' THEN tm.value END)/1000000 AS cpu_time_secs FROM dba_hist_sys_time_model tm JOIN dba_hist_snapshot s ON tm.snap_id = s.snap_id AND tm.instance_number = s.instance_number WHERE s.begin_interval_time > SYSDATE - &DAYS_AGO AND tm.stat_name IN ('DB time', 'DB CPU') GROUP BY s.snap_id, s.begin_interval_time, s.instance_number ), io_stats AS ( SELECT ss.snap_id, ss.instance_number, (SUM(CASE WHEN ss.stat_name IN ('physical read bytes', 'physical write bytes') THEN ss.value ELSE 0 END)/1024/1024) AS io_mb FROM dba_hist_sysstat ss WHERE ss.stat_name IN ('physical read bytes', 'physical write bytes') GROUP BY ss.snap_id, ss.instance_number ), instance_names AS ( SELECT DISTINCT instance_number, instance_name FROM gv$instance ), combined AS ( SELECT w.snap_id, w.begin_interval_time, w.instance_number, w.db_time_secs, w.cpu_time_secs, COALESCE(i.io_mb, 0) AS io_mb FROM workload w LEFT JOIN io_stats i ON w.snap_id = i.snap_id AND w.instance_number = i.instance_number ) SELECT TO_CHAR(c.begin_interval_time, 'YYYY-MM-DD HH24:MI') AS begin_time, i.instance_name, ROUND(c.db_time_secs, 2) AS db_time_secs, ROUND(c.cpu_time_secs, 2) AS cpu_time_secs, ROUND(c.io_mb, 2) AS io_mb, CASE WHEN c.db_time_secs > (SELECT AVG(db_time_secs)*1.5 FROM combined c2 WHERE c2.begin_interval_time = c.begin_interval_time) THEN 'CRITICAL' WHEN c.db_time_secs > (SELECT AVG(db_time_secs)*1.2 FROM combined c2 WHERE c2.begin_interval_time = c.begin_interval_time) THEN 'WARNING' WHEN c.io_mb > (SELECT AVG(io_mb)*1.5 FROM combined c3 WHERE c3.begin_interval_time = c.begin_interval_time) THEN 'CRITICAL' WHEN c.io_mb > (SELECT AVG(io_mb)*1.2 FROM combined c3 WHERE c3.begin_interval_time = c.begin_interval_time) THEN 'WARNING' ELSE 'OK' END AS skew_status FROM combined c LEFT JOIN instance_names i ON c.instance_number = i.instance_number ORDER BY c.begin_interval_time DESC, i.instance_name; SET PAGESIZE 100 SET LINESIZE 200 COLUMN originating_timestamp FORMAT A30 COLUMN message_text FORMAT A100 COLUMN inst_id FORMAT 99 COLUMN status FORMAT A10 PROMPT === ORA- ERRORS IN ALERT LOG (LAST 3 HOURS) - ORACLE 19C RAC === SELECT inst_id, originating_timestamp, message_text, CASE WHEN LOWER(message_text) LIKE '%ora-%' THEN 'CRITICAL' ELSE 'OK' END AS status FROM gv$diag_alert_ext WHERE originating_timestamp > SYSDATE - 3/24 AND LOWER(message_text) LIKE '%ora-%' ORDER BY originating_timestamp DESC; SET PAGESIZE 100 SET LINESIZE 200 COLUMN sample_time FORMAT A20 COLUMN event FORMAT A40 COLUMN wait_class FORMAT A20 COLUMN instance FORMAT 99 COLUMN count FORMAT 99999 COLUMN status FORMAT A10 PROMPT === TOP WAIT EVENTS (LAST &MINUTES_AGO MINUTES AND &HOURS_AGO HOURS) - ORACLE 19C RAC === -- Last &MINUTES_AGO Minutes PROMPT PROMPT --- Top Wait Events (Last &MINUTES_AGO Minutes) --- SELECT TO_CHAR(sample_time, 'YYYY-MM-DD HH24:MI') AS sample_time, event, wait_class, instance_number AS instance, COUNT(*) AS count, CASE WHEN COUNT(*) > 100 THEN 'CRITICAL' WHEN COUNT(*) > 50 THEN 'WARNING' ELSE 'OK' END AS status FROM dba_hist_active_sess_history WHERE sample_time >= SYSDATE - (&MINUTES_AGO / (24 * 60)) GROUP BY TO_CHAR(sample_time, 'YYYY-MM-DD HH24:MI'), event, wait_class, instance_number ORDER BY count DESC FETCH FIRST 10 ROWS ONLY; -- Last &HOURS_AGO Hours PROMPT PROMPT --- Top Wait Events (Last &HOURS_AGO Hours) --- SELECT TO_CHAR(sample_time, 'YYYY-MM-DD HH24') AS sample_hour, event, wait_class, instance_number AS instance, COUNT(*) AS count, CASE WHEN COUNT(*) > 500 THEN 'CRITICAL' WHEN COUNT(*) > 200 THEN 'WARNING' ELSE 'OK' END AS status FROM dba_hist_active_sess_history WHERE sample_time >= SYSDATE - (&HOURS_AGO / 24) GROUP BY TO_CHAR(sample_time, 'YYYY-MM-DD HH24'), event, wait_class, instance_number ORDER BY count DESC FETCH FIRST 10 ROWS ONLY; SET PAGESIZE 100 SET LINESIZE 200 COLUMN inst_id FORMAT 99 COLUMN sid FORMAT 99999 COLUMN username FORMAT A15 COLUMN requested_degree FORMAT 999 COLUMN actual_degree FORMAT 999 COLUMN sql_id FORMAT A15 COLUMN program FORMAT A25 COLUMN status FORMAT A10 PROMPT === DOWNGRADED PARALLEL SESSIONS - ORACLE 19C === SELECT px.inst_id, px.sid, s.username, px.requested_dop AS requested_degree, px.degree AS actual_degree, s.sql_id, s.program, CASE WHEN px.degree < px.requested_dop THEN 'CRITICAL' ELSE 'OK' END AS status FROM gv$px_session px JOIN gv$session s ON px.sid = s.sid AND px.inst_id = s.inst_id WHERE px.requested_dop > 0 AND px.degree < px.requested_dop ORDER BY px.inst_id, px.sid; SET PAGESIZE 100 SET LINESIZE 200 COLUMN inst_id FORMAT 99 COLUMN sid FORMAT 99999 COLUMN serial# FORMAT 99999 COLUMN username FORMAT A15 COLUMN status FORMAT A10 COLUMN logon_time FORMAT A20 COLUMN run_mins FORMAT 999999.99 COLUMN sql_id FORMAT A15 COLUMN event FORMAT A40 PROMPT === LONG RUNNING SESSIONS (ACTIVE > 1 HOUR) - ORACLE 19C RAC === SELECT s.inst_id, s.sid, s.serial#, s.username, s.status, TO_CHAR(s.logon_time, 'YYYY-MM-DD HH24:MI:SS') AS logon_time, ROUND((SYSDATE - s.logon_time) * 24 * 60, 2) AS run_mins, s.sql_id, s.event, CASE WHEN (SYSDATE - s.logon_time) * 24 * 60 > 60 THEN 'CRITICAL' ELSE 'OK' END AS status FROM gv$session s WHERE s.status = 'ACTIVE' AND s.username IS NOT NULL AND s.logon_time < SYSDATE - 1/24 ORDER BY s.inst_id, run_mins DESC; run_sql_file_report() { local title="$1" local sql_file="$2" local connect_str="$3" # Temporary file to capture SQL output local tmp_output=$(mktemp) local section_status="OK" sqlplus -s "$connect_str" < "$tmp_output" SET LINESIZE 200 SET PAGESIZE 100 SET FEEDBACK OFF SET VERIFY OFF DEFINE DAYS_AGO=$DAYS_AGO DEFINE HOURS_AGO=$HOURS_AGO DEFINE MINUTES_AGO=$MINUTES_AGO @$sql_file EXIT EOF # Detect CRITICAL or WARNING for section-level status if grep -q 'CRITICAL' "$tmp_output"; then section_status="CRITICAL" elif grep -q 'WARNING' "$tmp_output"; then section_status="WARNING" else section_status="OK" fi echo "

$title - Status: $section_status

" >> "$HTML_REPORT"

    while IFS= read -r line; do
        if echo "$line" | grep -q 'CRITICAL'; then
            line=$(echo "$line" | sed 's/CRITICAL/CRITICAL<\/b><\/span>/g')
        elif echo "$line" | grep -q 'WARNING'; then
            line=$(echo "$line" | sed 's/WARNING/WARNING<\/b><\/span>/g')
        elif echo "$line" | grep -q 'OK'; then
            line=$(echo "$line" | sed 's/OK/OK<\/b><\/span>/g')
        fi
        echo "$line" >> "$HTML_REPORT"
    done < "$tmp_output"

    rm -f "$tmp_output"
    echo "
" >> "$HTML_REPORT" } SET PAGESIZE 100 SET LINESIZE 200 COLUMN begin_time FORMAT A20 COLUMN instance_name FORMAT A20 COLUMN avg_latency FORMAT 999.99 COLUMN status FORMAT A10 PROMPT === IO RESPONSE TIME (AVG SYNC SINGLE-BLOCK READ LATENCY) - ORACLE 19C === WITH io_latency AS ( SELECT s.begin_interval_time, s.instance_number, h.metric_name, AVG(h.value) AS avg_latency FROM dba_hist_sysmetric_history h JOIN dba_hist_snapshot s ON h.snap_id = s.snap_id AND h.instance_number = s.instance_number WHERE h.metric_name = 'Average Synchronous Single-Block Read Latency' AND s.begin_interval_time >= SYSDATE - &DAYS_AGO GROUP BY s.begin_interval_time, s.instance_number, h.metric_name ), instances AS ( SELECT DISTINCT instance_number, instance_name FROM gv$instance ) SELECT TO_CHAR(i.begin_interval_time, 'YYYY-MM-DD HH24:MI') AS begin_time, n.instance_name, ROUND(i.avg_latency, 2) AS avg_latency, CASE WHEN i.avg_latency > 20 THEN 'CRITICAL' WHEN i.avg_latency > 10 THEN 'WARNING' ELSE 'OK' END AS status FROM io_latency i LEFT JOIN instances n ON i.instance_number = n.instance_number ORDER BY i.begin_interval_time DESC, n.instance_name; SET PAGESIZE 100 SET LINESIZE 200 COLUMN begin_time FORMAT A20 COLUMN instance_name FORMAT A20 COLUMN aas FORMAT 999.99 COLUMN db_time_secs FORMAT 999999.99 COLUMN status FORMAT A10 WITH db_time_data AS ( SELECT s.snap_id, s.instance_number, s.begin_interval_time, s.end_interval_time, MAX(CASE WHEN tm.stat_name = 'DB time' THEN tm.value END) AS db_time FROM dba_hist_sys_time_model tm JOIN dba_hist_snapshot s ON tm.snap_id = s.snap_id AND tm.instance_number = s.instance_number WHERE s.begin_interval_time > SYSDATE - &DAYS_AGO GROUP BY s.snap_id, s.instance_number, s.begin_interval_time, s.end_interval_time ), instance_names AS ( SELECT DISTINCT instance_number, instance_name FROM gv$instance ) SELECT TO_CHAR(d.begin_interval_time, 'YYYY-MM-DD HH24:MI') AS begin_time, i.instance_name, ROUND(d.db_time / 1000000, 2) AS db_time_secs, ROUND((d.db_time / 1000000) / (EXTRACT(SECOND FROM (d.end_interval_time - d.begin_interval_time) DAY TO SECOND)), 2) AS aas, CASE WHEN ROUND((d.db_time / 1000000) / (EXTRACT(SECOND FROM (d.end_interval_time - d.begin_interval_time) DAY TO SECOND)), 2) > 4 THEN 'CRITICAL' WHEN ROUND((d.db_time / 1000000) / (EXTRACT(SECOND FROM (d.end_interval_time - d.begin_interval_time) DAY TO SECOND)), 2) > 2 THEN 'WARNING' ELSE 'OK' END AS status FROM db_time_data d LEFT JOIN instance_names i ON d.instance_number = i.instance_number ORDER BY d.begin_interval_time DESC, i.instance_name; SET PAGESIZE 100 SET LINESIZE 200 COLUMN sql_id FORMAT A15 COLUMN executions FORMAT 9999999 COLUMN cpu_time FORMAT 9999999.99 COLUMN avg_cpu FORMAT 999999.99 COLUMN module FORMAT A20 PROMPT === TOP 5 SQLs BY CPU TIME === SELECT * FROM ( SELECT sql_id, executions_delta AS executions, cpu_time_delta/1000000 AS cpu_time, ROUND((cpu_time_delta/1000000)/NULLIF(executions_delta, 0), 2) AS avg_cpu, module FROM dba_hist_sqlstat WHERE executions_delta > 0 ORDER BY cpu_time_delta DESC ) WHERE ROWNUM <= 5; SET PAGESIZE 100 SET LINESIZE 200 COLUMN sql_id FORMAT A15 COLUMN executions FORMAT 9999999 COLUMN disk_reads FORMAT 9999999 COLUMN avg_io FORMAT 999999.99 COLUMN module FORMAT A20 PROMPT === TOP 5 SQLs BY DISK READS === SELECT * FROM ( SELECT sql_id, executions_delta AS executions, disk_reads_delta AS disk_reads, ROUND(disk_reads_delta / NULLIF(executions_delta, 0), 2) AS avg_io, module FROM dba_hist_sqlstat WHERE executions_delta > 0 ORDER BY disk_reads_delta DESC ) WHERE ROWNUM <= 5; SET PAGESIZE 100 SET LINESIZE 200 COLUMN target_mb FORMAT 99999 COLUMN est_extra_rw_mb FORMAT 99999.99 COLUMN est_cache_hit FORMAT 999.99 COLUMN advice FORMAT A10 PROMPT === PGA TARGET ADVICE === SELECT ROUND(pga_target_for_estimate / 1024 / 1024) AS target_mb, ROUND(estd_extra_bytes_rw / 1024 / 1024, 2) AS est_extra_rw_mb, estd_cache_hit_percent AS est_cache_hit, CASE WHEN estd_cache_hit_percent >= 99 THEN 'OK' WHEN estd_cache_hit_percent >= 90 THEN 'WARNING' ELSE 'CRITICAL' END AS advice FROM v$pga_target_advice WHERE estd_cache_hit_percent IS NOT NULL ORDER BY target_mb; PROMPT PROMPT === SGA TARGET ADVICE === SELECT ROUND(sga_size/1024) AS target_mb, estd_db_time/100 AS est_db_time_seconds, estd_physical_reads, CASE WHEN estd_db_time <= MIN(estd_db_time) OVER () THEN 'OK' WHEN estd_db_time <= MIN(estd_db_time) OVER () * 1.1 THEN 'WARNING' ELSE 'CRITICAL' END AS advice FROM v$sga_target_advice WHERE sga_size_factor BETWEEN 0.5 AND 2 ORDER BY sga_size; SET PAGESIZE 100 SET LINESIZE 200 COLUMN instance FORMAT 99 COLUMN pga_alloc_mb FORMAT 999999.99 COLUMN sga_mem_mb FORMAT 999999.99 COLUMN status FORMAT A10 WITH pga AS ( SELECT inst_id, ROUND(SUM(value)/1024/1024, 2) AS pga_alloc_mb FROM gv$pgastat WHERE name = 'total PGA allocated' GROUP BY inst_id ), sga AS ( SELECT inst_id, ROUND(SUM(value)/1024/1024, 2) AS sga_mem_mb FROM gv$sga GROUP BY inst_id ) SELECT p.inst_id AS instance, p.pga_alloc_mb, s.sga_mem_mb, CASE WHEN p.pga_alloc_mb > 2048 THEN 'CRITICAL' WHEN p.pga_alloc_mb > 1024 THEN 'WARNING' ELSE 'OK' END AS status FROM pga p JOIN sga s ON p.inst_id = s.inst_id ORDER BY p.inst_id; SET PAGESIZE 100 SET LINESIZE 200 COLUMN instance FORMAT 99 COLUMN pga_alloc_mb FORMAT 999999.99 COLUMN sga_mem_mb FORMAT 999999.99 COLUMN status FORMAT A10 WITH pga AS ( SELECT inst_id, ROUND(SUM(value)/1024/1024, 2) AS pga_alloc_mb FROM gv$pgastat WHERE name = 'total PGA allocated' GROUP BY inst_id ), sga AS ( SELECT inst_id, ROUND(SUM(value)/1024/1024, 2) AS sga_mem_mb FROM gv$sga GROUP BY inst_id ) SELECT p.inst_id AS instance, p.pga_alloc_mb, s.sga_mem_mb, CASE WHEN p.pga_alloc_mb > 2048 THEN 'CRITICAL' WHEN p.pga_alloc_mb > 1024 THEN 'WARNING' ELSE 'OK' END AS status FROM pga p JOIN sga s ON p.inst_id = s.inst_id ORDER BY p.inst_id; WITH pga AS ( SELECT inst_id, ROUND(SUM(value)/1024/1024, 2) AS pga_alloc_mb FROM gv$pgastat WHERE name = 'total PGA allocated' GROUP BY inst_id ), sga AS ( SELECT inst_id, ROUND(SUM(bytes)/1024/1024, 2) AS sga_mem_mb FROM gv$sgainfo WHERE name = 'Total SGA Size' GROUP BY inst_id ) SELECT p.inst_id AS instance, p.pga_alloc_mb, s.sga_mem_mb, CASE WHEN p.pga_alloc_mb > 2048 THEN 'CRITICAL' WHEN p.pga_alloc_mb > 1024 THEN 'WARNING' ELSE 'OK' END AS status FROM pga p JOIN sga s ON p.inst_id = s.inst_id ORDER BY p.inst_id; SET PAGESIZE 100 SET LINESIZE 200 COLUMN begin_time FORMAT A20 COLUMN instance_name FORMAT A15 COLUMN aas FORMAT 999.99 COLUMN db_time_secs FORMAT 999999.99 COLUMN status FORMAT A10 SELECT TO_CHAR(s.begin_interval_time, 'YYYY-MM-DD HH24:MI') AS begin_time, i.instance_name, ROUND(tm.value / 1000000, 2) AS db_time_secs, ROUND((tm.value / 1000000) / (EXTRACT(SECOND FROM (s.end_interval_time - s.begin_interval_time) DAY TO SECOND)), 2) AS aas, CASE WHEN ROUND((tm.value / 1000000) / (EXTRACT(SECOND FROM (s.end_interval_time - s.begin_interval_time) DAY TO SECOND)), 2) > 4 THEN 'CRITICAL' WHEN ROUND((tm.value / 1000000) / (EXTRACT(SECOND FROM (s.end_interval_time - s.begin_interval_time) DAY TO SECOND)), 2) > 2 THEN 'WARNING' ELSE 'OK' END AS status FROM dba_hist_sys_time_model tm JOIN dba_hist_snapshot s ON tm.snap_id = s.snap_id AND tm.instance_number = s.instance_number JOIN gv$instance i ON i.inst_id = tm.instance_number WHERE tm.stat_name = 'DB time' AND s.begin_interval_time >= SYSDATE - &DAYS_AGO ORDER BY s.begin_interval_time, i.instance_name; SET PAGESIZE 100 SET LINESIZE 200 COLUMN sql_id FORMAT A15 COLUMN plan_hash_value FORMAT 9999999999 COLUMN execs FORMAT 999999 COLUMN avg_etime FORMAT 99999.99 COLUMN module FORMAT A20 SELECT sql_id, COUNT(DISTINCT plan_hash_value) AS plan_count, MIN(plan_hash_value) KEEP (DENSE_RANK FIRST ORDER BY elapsed_time_total DESC) AS sample_plan, SUM(executions_delta) AS execs, ROUND(SUM(elapsed_time_delta)/1000000/NULLIF(SUM(executions_delta), 0), 2) AS avg_etime_secs, MIN(module) AS module FROM dba_hist_sqlstat WHERE snap_id IN ( SELECT snap_id FROM dba_hist_snapshot WHERE begin_interval_time > SYSDATE - 1) GROUP BY sql_id HAVING COUNT(DISTINCT plan_hash_value) > 1 ORDER BY plan_count DESC, execs DESC FETCH FIRST 20 ROWS ONLY; SET PAGESIZE 100 SET LINESIZE 200 SELECT name, network_name, creation_date FROM dba_services WHERE enabled = 'TRUE'; SET PAGESIZE 100 SET LINESIZE 200 SELECT inst_id, COUNT(*) AS active_sessions FROM gv$session WHERE status = 'ACTIVE' GROUP BY inst_id ORDER BY inst_id; SET PAGESIZE 100 SET LINESIZE 200 SELECT sid, username, degree, requested_degree, sql_id, program FROM gv$px_session WHERE degree < requested_degree ORDER BY degree; SET PAGESIZE 100 SET LINESIZE 200 SELECT session_id, instance_number, username, action_name, error_number, error_message, event_timestamp FROM dba_audit_session WHERE returncode != 0 AND event_timestamp > SYSDATE - 1/24 ORDER BY event_timestamp DESC; SET PAGESIZE 100 SET LINESIZE 200 SELECT s.inst_id, s.sid, s.serial#, s.username, s.program, s.sql_id, s.status, ROUND((SYSDATE - s.logon_time) * 24 * 60, 2) AS minutes_active FROM gv$session s WHERE s.status = 'ACTIVE' AND s.type = 'USER' AND s.logon_time > SYSDATE - 1/24 ORDER BY minutes_active DESC FETCH FIRST 10 ROWS ONLY; SET PAGESIZE 100 SET LINESIZE 200 SELECT i.owner, i.index_name, i.index_type, i.status, i.last_analyzed, p.partition_name FROM dba_indexes i LEFT JOIN dba_ind_partitions p ON i.index_name = p.index_name AND i.owner = p.index_owner WHERE i.status = 'UNUSABLE' OR (p.status = 'UNUSABLE' AND p.last_analyzed > SYSDATE - 1/24) ORDER BY i.owner, i.index_name; SET PAGESIZE 100 SET LINESIZE 200 SELECT owner, object_name, object_type, status, last_ddl_time FROM dba_objects WHERE status = 'INVALID' AND last_ddl_time > SYSDATE - 1/24 ORDER BY last_ddl_time DESC; SET PAGESIZE 100 SET LINESIZE 200 SELECT l.inst_id, s.sid, s.serial#, s.username, o.object_name, o.object_type, l.type, l.lmode, l.request, l.block FROM gv$locked_object l JOIN dba_objects o ON l.object_id = o.object_id JOIN gv$session s ON l.session_id = s.sid AND l.inst_id = s.inst_id ORDER BY l.inst_id, s.sid; 2 SET PAGESIZE 100 COLUMN day FORMAT A15 COLUMN session_state FORMAT A10 COLUMN service_name FORMAT A25 COLUMN username FORMAT A15 SELECT day, service_name, username, session_state, count, CASE WHEN session_state = 'ACTIVE' AND count > 80 THEN 'CRITICAL' WHEN session_state = 'ACTIVE' AND count > 20 THEN 'WARNING' ELSE 'OK' END AS status FROM ( SELECT TO_CHAR(sample_time, 'YYYY-MM-DD') AS day, service_hash, session_state, NVL(service_name, 'UNKNOWN') AS service_name, NVL(username, 'UNKNOWN') AS username, COUNT(*) AS count FROM gv$active_session_history WHERE sample_time > SYSDATE - &DAYS_AGO AND session_type = 'FOREGROUND' GROUP BY TO_CHAR(sample_time, 'YYYY-MM-DD'), session_state, service_name, service_hash, username ) ORDER BY day, service_name, session_state; #!/bin/bash # ========== CONFIGURATION ========== if [ -z "$1" ]; then echo "Usage: $0 user/password@db" exit 1 fi ORACLE_USER="system" ORACLE_PASS="your_password" ORACLE_SID="ORCL" EMAIL_TO="dba-team@example.com" EMAIL_SUBJECT="Oracle RAC Health Check Report - $(date '+%Y-%m-%d %H:%M')" HTML_REPORT="./output/health_check_$(date +%Y%m%d_%H%M%S).html" DB_CONNECT_STRING="$1" # ========== ANALYSIS WINDOWS ========== DAYS_AGO=7 HOURS_AGO=1 MINUTES_AGO=5 # ========== THRESHOLDS ========== MAX_SESSIONS_UTIL=90 MAX_PROCESSES_UTIL=90 DB_LOAD_THRESHOLD=85 MAX_BLOCKED_SESSIONS=0 MAX_IO_RESP_MS=20 # ========== DB CONNECTIVITY VALIDATION ========== check_db_connectivity() { echo "Validating database connectivity..." sqlplus -s "$DB_CONNECT_STRING" < /dev/null WHENEVER SQLERROR EXIT FAILURE SELECT 'Connection Successful' FROM dual; EXIT EOF if [ $? -ne 0 ]; then echo "❌ ERROR: Cannot connect to Oracle DB with provided credentials." exit 1 else echo "✅ Database connection successful." fi } # ========== HTML OUTPUT ========== init_html_report() { echo "Oracle RAC Health Check" > "$HTML_REPORT" echo "

Oracle RAC Health Check Report

" >> "$HTML_REPORT" echo "

Generated on: $(date)

" >> "$HTML_REPORT" } close_html_report() { echo "" >> "$HTML_REPORT" } append_section() { local title="$1" echo "

$title

" >> "$HTML_REPORT"
}

run_sql_file_report() {
    local title="$1"
    local sql_file="$2"
    local connect_str="$3"
    append_section "$title"
    while IFS= read -r line; do
        if echo "$line" | grep -q 'CRITICAL'; then
            line=$(echo "$line" | sed 's/CRITICAL/CRITICAL<\/b><\/span>/g')
        elif echo "$line" | grep -q 'WARNING'; then
            line=$(echo "$line" | sed 's/WARNING/WARNING<\/b><\/span>/g')
        elif echo "$line" | grep -q 'OK'; then
            line=$(echo "$line" | sed 's/OK/OK<\/b><\/span>/g')
        fi
        echo "$line" >> "$HTML_REPORT"
    done < <(
        sqlplus -s "$connect_str" <" >> "$HTML_REPORT"
}

send_email_report() {
    if command -v mailx &>/dev/null; then
        cat "$HTML_REPORT" | mailx -a "Content-type: text/html" -s "$EMAIL_SUBJECT" "$EMAIL_TO"
    else
        echo "mailx not found. Please install or configure an alternative mail agent."
    fi
}

# ========== MAIN ==========
main() {
    check_db_connectivity
    init_html_report

    # Example: run_sql_file_report "DB Load" "sql/01_db_load.sql"
    # Add more report sections here as needed

    close_html_report
    send_email_report
}

main

SET PAGESIZE 100
COLUMN day FORMAT A15
COLUMN session_state FORMAT A10
COLUMN service_name FORMAT A25
COLUMN username FORMAT A15

SELECT day,
       service_name,
       username,
       session_state,
       count,
       CASE 
         WHEN session_state = 'ACTIVE' AND count > 80 THEN 'CRITICAL'
         WHEN session_state = 'ACTIVE' AND count > 20 THEN 'WARNING'
         ELSE 'OK'
       END AS status
FROM (
    SELECT TO_CHAR(sample_time, 'YYYY-MM-DD') AS day,
           service_hash,
           session_state,
           NVL(service_name, 'UNKNOWN') AS service_name,
           NVL(username, 'UNKNOWN') AS username,
           COUNT(*) AS count
      FROM gv$active_session_history
     WHERE sample_time > SYSDATE - &DAYS_AGO
       AND session_type = 'FOREGROUND'
     GROUP BY TO_CHAR(sample_time, 'YYYY-MM-DD'), session_state, service_name, service_hash, username
)
ORDER BY day, service_name, session_state;


SET PAGESIZE 100
SET LINESIZE 200
COLUMN originating_timestamp FORMAT A30
COLUMN message_text FORMAT A100

SELECT originating_timestamp,
       message_text
  FROM x$dbgalertext
 WHERE originating_timestamp > SYSDATE - 3/24
   AND message_text LIKE 'ORA-%'
 ORDER BY originating_timestamp DESC;


run_sql_file_report() {
    local title="$1"
    local sql_file="$2"
    append_section "$title"
    while IFS= read -r line; do
        if echo "$line" | grep -q 'CRITICAL'; then
            line=$(echo "$line" | sed 's/CRITICAL/CRITICAL<\/b><\/span>/g')
        elif echo "$line" | grep -q 'WARNING'; then
            line=$(echo "$line" | sed 's/WARNING/WARNING<\/b><\/span>/g')
        fi
        echo "$line" >> "$HTML_REPORT"
    done < <(
        sqlplus -s "$DB_CONNECT_STRING" <" >> "$HTML_REPORT"
}




4



healder


  Oracle RAC Health Check Report
  




20_ashtop_5min.sql:

sql
Copy
Edit
SET PAGESIZE 100
SET LINESIZE 200
@sql/ashtop.sql username,sql_id session_type='FOREGROUND' sysdate - 5/(24*60) sysdate
21_ashtop_1hr.sql:

sql
Copy
Edit
SET PAGESIZE 100
SET LINESIZE 200
@sql/ashtop.sql username,sql_id session_type='FOREGROUND' sysdate - 1/24 sysdate

1
SET PAGESIZE 500
SET LINESIZE 200
COLUMN day FORMAT A10
SELECT TO_CHAR(begin_time, 'YYYY-MM-DD') day,
       ROUND((db_time/elapsed_seconds)*100, 2) AS db_load,
       CASE
         WHEN ROUND((db_time/elapsed_seconds)*100, 2) > 90 THEN 'CRITICAL'
         WHEN ROUND((db_time/elapsed_seconds)*100, 2) > 70 THEN 'WARNING'
         ELSE 'OK'
       END AS status
  FROM dba_hist_database_instance
 WHERE begin_time >= SYSDATE - &DAYS_AGO
 ORDER BY begin_time;

2
SET PAGESIZE 100
SELECT day,
       session_state,
       count,
       CASE 
         WHEN session_state = 'ACTIVE' AND count > 80 THEN 'CRITICAL'
         WHEN session_state = 'ACTIVE' AND count > 20 THEN 'WARNING'
         ELSE 'OK'
       END AS status
FROM (
    SELECT TO_CHAR(sample_time, 'YYYY-MM-DD') day,
           session_state,
           COUNT(*) AS count
      FROM dba_hist_active_sess_history
     WHERE sample_time > SYSDATE - &DAYS_AGO
     GROUP BY TO_CHAR(sample_time, 'YYYY-MM-DD'), session_state
)
ORDER BY day, session_state;
3
SET PAGESIZE 100
SELECT inst_id,
       resource_name,
       current_utilization,
       max_utilization,
       limit_value,
       ROUND((current_utilization/limit_value)*100,2) AS utilization_pct,
       CASE
         WHEN ROUND((current_utilization/limit_value)*100,2) > 90 THEN 'CRITICAL'
         WHEN ROUND((current_utilization/limit_value)*100,2) > 80 THEN 'WARNING'
         ELSE 'OK'
       END AS status
  FROM gv$resource_limit
 WHERE resource_name IN ('sessions', 'processes')
 ORDER BY inst_id, resource_name;
4
SET PAGESIZE 100
SELECT TO_CHAR(snap.begin_interval_time, 'YYYY-MM-DD') snap_time,
       ROUND((pga.pga_alloc_mem)/1024/1024) AS pga_mb,
       ROUND((sga.value)/1024/1024) AS sga_mb
  FROM dba_hist_pgastat pga,
       dba_hist_snapshot snap,
       (SELECT snap_id, instance_number, value FROM dba_hist_sysstat WHERE stat_name = 'SGA memory') sga
 WHERE pga.snap_id = snap.snap_id
   AND sga.snap_id = snap.snap_id
   AND snap.begin_interval_time > SYSDATE - &DAYS_AGO
   AND rownum <= 30;

5
SET PAGESIZE 100
SELECT inst_id,
       sid, serial#,
       blocking_session,
       blocking_instance,
       wait_class, seconds_in_wait, event,
       CASE
         WHEN blocking_session IS NOT NULL THEN 'CRITICAL'
         ELSE 'OK'
       END AS status
  FROM gv$session
 WHERE blocking_session IS NOT NULL;

6
                   
SET PAGESIZE 100
SELECT inst_id,
       sid, serial#, username, status, logon_time,
       ROUND((SYSDATE - logon_time)*24, 2) AS hours_logged_in,
       CASE
         WHEN ROUND((SYSDATE - logon_time)*24, 2) > 1 THEN 'CRITICAL'
         ELSE 'OK'
       END AS status
  FROM gv$session
 WHERE status = 'ACTIVE' AND logon_time < SYSDATE - 1/24;

7
                                                 
##
SET PAGESIZE 100
SELECT inst_id, name,
       ROUND(phyblkrd/time_waited_read_micro, 2) AS read_resp_ms,
       ROUND(phyblkwrt/time_waited_write_micro, 2) AS write_resp_ms
  FROM gv$iostat_file;
                                               
                                                 
                                                 
                                                 7
               8
            SET PAGESIZE 100
SELECT inst_id, event, COUNT(*) AS event_count
  FROM gv$active_session_history
 WHERE sample_time > SYSDATE - &HOURS_AGO/24
 GROUP BY inst_id, event
 ORDER BY inst_id, event_count DESC;

SELECT inst_id, event, COUNT(*) AS event_count
  FROM gv$active_session_history
 WHERE sample_time > SYSDATE - &MINUTES_AGO/(24*60)
 GROUP BY inst_id, event
 ORDER BY inst_id, event_count DESC;
     9
     SET PAGESIZE 100
SELECT inst_id,
       ROUND(SUM(db_time_delta)/60,2) AS db_time_mins,
       ROUND(SUM(cpu_time_delta)/60,2) AS cpu_time_mins,
       ROUND(SUM(logical_reads_delta)) AS logical_reads
  FROM dba_hist_sysmetric_summary
 GROUP BY inst_id
 ORDER BY db_time_mins DESC;
     10
     SET PAGESIZE 100
SELECT inst_id, event, COUNT(*) AS waits
  FROM gv$active_session_history
 WHERE sample_time > SYSDATE - &HOURS_AGO/24
 GROUP BY inst_id, event
 ORDER BY inst_id, waits DESC FETCH FIRST 10 ROWS WITH TIES;
11
     SET PAGESIZE 100
SELECT inst_id, name, value
  FROM gv$sysstat
 WHERE name IN ('gc cr blocks received', 'gc current blocks received',
                'gc cr block busy', 'gc current block busy',
                'gc cr block lost', 'gc current block lost')
 ORDER BY inst_id;

     
12
     SET PAGESIZE 100
SELECT *
  FROM (SELECT sql_id, plan_hash_value, elapsed_time_delta/1000000 elapsed_sec,
               executions_delta execs,
               module, sql_text
          FROM dba_hist_sqlstat NATURAL JOIN dba_hist_sqltext
         WHERE snap_id IN (SELECT MAX(snap_id) FROM dba_hist_snapshot WHERE begin_interval_time > SYSDATE - &HOURS_AGO/24)
         ORDER BY elapsed_sec DESC)
 WHERE ROWNUM <= 10;
                   
                   
                   13
                   SET PAGESIZE 100
SELECT tablespace_name,
       ROUND(used_space*8/1024) AS used_mb,
       ROUND((tablespace_size - used_space)*8/1024) AS free_mb,
       ROUND((used_space/tablespace_size)*100,2) AS pct_used
  FROM dba_tablespace_usage_metrics
 ORDER BY pct_used DESC;
                   14
                   SET PAGESIZE 100
SELECT name,
       ROUND(total_mb/1024) total_gb,
       ROUND(free_mb/1024) free_gb,
       ROUND((1-(free_mb/total_mb))*100,2) pct_used
  FROM v$asm_diskgroup;
                   
       15
                   SET PAGESIZE 100
SELECT inst_id,
       event,
       COUNT(*) AS wait_count,
       ROUND(AVG(wait_time)) AS avg_wait_ms
  FROM gv$active_session_history
 WHERE event = 'log file sync'
   AND sample_time > SYSDATE - &HOURS_AGO/24
 GROUP BY inst_id, event
 ORDER BY wait_count DESC;
     16
     SET PAGESIZE 100
SELECT inst_id, tablespace_name,
       ROUND(used_blocks*8192/1024/1024) used_mb
  FROM gv$sort_segment
 WHERE used_blocks > 0;
     
     
     17
     SET PAGESIZE 100
SELECT inst_id,
       ROUND((parse_calls/executions)*100, 2) AS parse_to_exec_pct,
       executions, parse_calls
  FROM gv$sql
 WHERE executions > 100
 ORDER BY parse_to_exec_pct DESC
 FETCH FIRST 10 ROWS WITH TIES;
     
     18
     SET PAGESIZE 100
SELECT TO_CHAR(first_time, 'YYYY-MM-DD HH24') AS hour,
       COUNT(*) AS switch_count
  FROM v$log_history
 WHERE first_time >= SYSDATE - &DAYS_AGO
 GROUP BY TO_CHAR(first_time, 'YYYY-MM-DD HH24')
 ORDER BY hour;
     
     19
     SET PAGESIZE 100
SELECT name,
       space_limit/1024/1024 AS limit_mb,
       space_used/1024/1024 AS used_mb,
       space_reclaimable/1024/1024 AS reclaimable_mb,
       ROUND((space_used/space_limit)*100, 2) AS pct_used,
       CASE
         WHEN ROUND((space_used/space_limit)*100, 2) > 95 THEN 'CRITICAL'
         WHEN ROUND((space_used/space_limit)*100, 2) > 85 THEN 'WARNING'
         ELSE 'OK'
       END AS status
  FROM v$recovery_file_dest;
###############
                                                 
                                                 send_email() {
  local host_entry="$1"
  local report_file="$2"

  local max_inline_size=2097152  # 2MB in bytes
  local file_size
  file_size=$(stat -c%s "$report_file")

  if [[ "$file_size" -le "$max_inline_size" ]]; then
    # Send as HTML body
    {
      echo "To: $EMAIL_RECIPIENT"
      echo "Subject: $EMAIL_SUBJECT - $host_entry"
      echo "Content-Type: text/html"
      echo
      cat "$report_file"
    } | sendmail -t
    log_msg "Email sent as inline HTML for $host_entry"
  else
    # Send as attachment
    {
      echo "To: $EMAIL_RECIPIENT"
      echo "Subject: $EMAIL_SUBJECT - $host_entry (Attached)"
      echo "MIME-Version: 1.0"
      echo "Content-Type: multipart/mixed; boundary="MIXED-BOUNDARY""
      echo
      echo "--MIXED-BOUNDARY"
      echo "Content-Type: text/plain"
      echo
      echo "Health check report is attached for $host_entry (size exceeds 2MB)."
      echo
      echo "--MIXED-BOUNDARY"
      echo "Content-Type: text/html; name="$(basename "$report_file")""
      echo "Content-Disposition: attachment; filename="$(basename "$report_file")""
      echo "Content-Transfer-Encoding: base64"
      echo
      base64 "$report_file"
      echo "--MIXED-BOUNDARY--"
    } | sendmail -t
    log_msg "Email sent with attachment for $host_entry (file too large)"
  fi
}


#!/bin/bash

# ========== CONFIGURATION ==========
ORACLE_USER="system"
ORACLE_PASS="your_password"
ORACLE_SID="ORCL"
EMAIL_TO="dba-team@example.com"
EMAIL_SUBJECT="Oracle RAC Health Check Report - $(date '+%Y-%m-%d %H:%M')"
HTML_REPORT="./output/health_check_$(date +%Y%m%d_%H%M%S).html"
DB_CONNECT_STRING="$ORACLE_USER/$ORACLE_PASS@$ORACLE_SID"

# ========== ANALYSIS WINDOWS ==========
DAYS_AGO=7
HOURS_AGO=1
MINUTES_AGO=5

# ========== THRESHOLDS ==========
MAX_SESSIONS_UTIL=90
MAX_PROCESSES_UTIL=90
DB_LOAD_THRESHOLD=85
MAX_BLOCKED_SESSIONS=0
MAX_IO_RESP_MS=20

# ========== FUNCTIONS ==========
init_html_report() {
    cat templates/report_header.html > "$HTML_REPORT"
    echo "

Oracle RAC Health Check Report

" >> "$HTML_REPORT" echo "

Generated on: $(date)

" >> "$HTML_REPORT" } close_html_report() { cat templates/report_footer.html >> "$HTML_REPORT" } append_section() { local title="$1" echo "

$title

" >> "$HTML_REPORT"
}

run_sql_file_report() {
    local title="$1"
    local sql_file="$2"
    append_section "$title"
    while IFS= read -r line; do
        if echo "$line" | grep -q 'CRITICAL'; then
            line=$(echo "$line" | sed 's/CRITICAL/CRITICAL<\/span>/g')
        elif echo "$line" | grep -q 'WARNING'; then
            line=$(echo "$line" | sed 's/WARNING/WARNING<\/span>/g')
        fi
        echo "$line" >> "$HTML_REPORT"
    done < <(sqlplus -s "$DB_CONNECT_STRING" @"$sql_file")
    echo "
" >> "$HTML_REPORT" } send_email_report() { if command -v mailx &>/dev/null; then cat "$HTML_REPORT" | mailx -a "Content-type: text/html" -s "$EMAIL_SUBJECT" "$EMAIL_TO" else echo "mailx not found. Please install or configure an alternative mail agent." fi } # ========== INDIVIDUAL CHECK FUNCTIONS ========== run_check_db_load() { run_sql_file_report "DB Load" "sql/01_db_load.sql"; } run_check_active_sessions() { run_sql_file_report "Active/Inactive Sessions" "sql/02_active_sessions.sql"; } run_check_session_processes() { run_sql_file_report "Session & Process Utilization" "sql/03_sessions_processes.sql"; } run_check_sga_pga_usage() { run_sql_file_report "SGA & PGA Usage" "sql/04_sga_pga_usage.sql"; } run_check_blocking_sessions() { run_sql_file_report "Blocking Sessions" "sql/05_blocking_sessions.sql"; } run_check_long_running_sessions() { run_sql_file_report "Long Running Sessions" "sql/06_long_running_sessions.sql"; } run_check_io_response_time() { run_sql_file_report "IO Response Time" "sql/07_io_response_time.sql"; } run_check_wait_events() { run_sql_file_report "Wait Events (1 Hour & 5 Min)" "sql/08_wait_events_window.sql"; } run_check_rac_instance_skew() { run_sql_file_report "RAC Instance Load Skew" "sql/09_rac_instance_skew.sql"; } run_check_top_waits() { run_sql_file_report "Top Wait Events by Instance" "sql/10_top_waits_by_instance.sql"; } run_check_global_cache() { run_sql_file_report "Global Cache Statistics" "sql/11_global_cache_stats.sql"; } run_check_top_sql() { run_sql_file_report "Top SQL by Elapsed Time" "sql/12_top_sql_elapsed.sql"; } run_check_tablespace_usage() { run_sql_file_report "Tablespace Usage" "sql/13_tablespace_usage.sql"; } run_check_asm_usage() { run_sql_file_report "ASM Diskgroup Usage" "sql/14_asm_diskgroup_usage.sql"; } run_check_log_sync() { run_sql_file_report "Redo Log Sync Waits" "sql/15_log_sync_contention.sql"; } run_check_temp_usage() { run_sql_file_report "Temp Usage" "sql/16_temp_usage.sql"; } run_check_parse_ratio() { run_sql_file_report "Parse to Execute Ratio" "sql/17_parse_to_exec_ratio.sql"; } run_check_log_switches() { run_sql_file_report "Log Switch History" "sql/18_log_switch_history.sql"; } run_check_fra_usage() { run_sql_file_report "FRA Space Usage" "sql/19_fra_usage.sql"; } run_check_ashtop_5min() { run_sql_file_report "ASH Top - Last 5 Min" "sql/20_ashtop_5min.sql"; } run_check_ashtop_1hr() { run_sql_file_report "ASH Top - Last 1 Hour" "sql/21_ashtop_1hr.sql"; } # ========== MAIN ========== main() { init_html_report run_check_db_load run_check_active_sessions run_check_session_processes run_check_sga_pga_usage run_check_blocking_sessions run_check_long_running_sessions run_check_io_response_time run_check_wait_events run_check_rac_instance_skew run_check_top_waits run_check_global_cache run_check_top_sql run_check_tablespace_usage run_check_asm_usage run_check_log_sync run_check_temp_usage run_check_parse_ratio run_check_log_switches run_check_fra_usage run_check_ashtop_5min run_check_ashtop_1hr close_html_report send_email_report } main ### SET PAGESIZE 500 SET LINESIZE 200 COLUMN day FORMAT A10 SELECT TO_CHAR(begin_time, 'YYYY-MM-DD') day, ROUND((db_time/elapsed_seconds)*100, 2) AS db_load, CASE WHEN ROUND((db_time/elapsed_seconds)*100, 2) > 90 THEN 'CRITICAL' WHEN ROUND((db_time/elapsed_seconds)*100, 2) > 70 THEN 'WARNING' ELSE 'OK' END AS status FROM dba_hist_database_instance WHERE begin_time >= SYSDATE - &DAYS_AGO ORDER BY begin_time; ## SET PAGESIZE 100 SELECT day, session_state, count, CASE WHEN session_state = 'ACTIVE' AND count > 80 THEN 'CRITICAL' WHEN session_state = 'ACTIVE' AND count > 20 THEN 'WARNING' ELSE 'OK' END AS status FROM ( SELECT TO_CHAR(sample_time, 'YYYY-MM-DD') day, session_state, COUNT(*) AS count FROM dba_hist_active_sess_history WHERE sample_time > SYSDATE - &DAYS_AGO GROUP BY TO_CHAR(sample_time, 'YYYY-MM-DD'), session_state ) ORDER BY day, session_state; ## SET PAGESIZE 100 SELECT TO_CHAR(snap.begin_interval_time, 'YYYY-MM-DD') snap_time, ROUND((pga.pga_alloc_mem)/1024/1024) AS pga_mb, ROUND((sga.value)/1024/1024) AS sga_mb FROM dba_hist_pgastat pga, dba_hist_snapshot snap, (SELECT snap_id, instance_number, value FROM dba_hist_sysstat WHERE stat_name = 'SGA memory') sga WHERE pga.snap_id = snap.snap_id AND sga.snap_id = snap.snap_id AND snap.begin_interval_time > SYSDATE - &DAYS_AGO AND rownum <= 30; ## SET PAGESIZE 100 SELECT name, space_limit/1024/1024 AS limit_mb, space_used/1024/1024 AS used_mb, space_reclaimable/1024/1024 AS reclaimable_mb, ROUND((space_used/space_limit)*100, 2) AS pct_used, CASE WHEN ROUND((space_used/space_limit)*100, 2) > 95 THEN 'CRITICAL' WHEN ROUND((space_used/space_limit)*100, 2) > 85 THEN 'WARNING' ELSE 'OK' END AS status FROM v$recovery_file_dest; SET PAGESIZE 100 SELECT inst_id, resource_name, current_utilization, max_utilization, limit_value, ROUND((current_utilization/limit_value)*100,2) AS utilization_pct, CASE WHEN ROUND((current_utilization/limit_value)*100,2) > 90 THEN 'CRITICAL' WHEN ROUND((current_utilization/limit_value)*100,2) > 80 THEN 'WARNING' ELSE 'OK' END AS status FROM gv$resource_limit WHERE resource_name IN ('sessions', 'processes') ORDER BY inst_id, resource_name; ## SELECT inst_id, resource_name, current_utilization, max_utilization, limit_value, ROUND((current_utilization/limit_value)*100,2) AS utilization_pct, CASE WHEN ROUND((current_utilization/limit_value)*100,2) > 90 THEN 'CRITICAL' WHEN ROUND((current_utilization/limit_value)*100,2) > 80 THEN 'WARNING' ELSE 'OK' END AS status FROM gv$resource_limit WHERE resource_name IN ('sessions', 'processes') ORDER BY inst_id, resource_name; ####### run_sql_file_report() { local title="$1" local sql_file="$2" append_section "$title" while IFS= read -r line; do if echo "$line" | grep -q 'CRITICAL'; then line=$(echo "$line" | sed 's/CRITICAL/CRITICAL<\/span>/g') elif echo "$line" | grep -q 'WARNING'; then line=$(echo "$line" | sed 's/WARNING/WARNING<\/span>/g') fi echo "$line" >> "$HTML_REPORT" done < <(sqlplus -s "$DB_CONNECT_STRING" @"$sql_file") echo "
" >> "$HTML_REPORT" } #!/bin/bash # ========== CONFIGURATION ========== ORACLE_USER="system" ORACLE_PASS="your_password" ORACLE_SID="ORCL" EMAIL_TO="dba-team@example.com" EMAIL_SUBJECT="Oracle RAC Health Check Report - $(date '+%Y-%m-%d %H:%M')" HTML_REPORT="./output/health_check_$(date +%Y%m%d_%H%M%S).html" DB_CONNECT_STRING="$ORACLE_USER/$ORACLE_PASS@$ORACLE_SID" # ========== ANALYSIS WINDOWS ========== DAYS_AGO=7 HOURS_AGO=1 MINUTES_AGO=5 # ========== THRESHOLDS ========== MAX_SESSIONS_UTIL=90 MAX_PROCESSES_UTIL=90 DB_LOAD_THRESHOLD=85 MAX_BLOCKED_SESSIONS=0 MAX_IO_RESP_MS=20 # ========== FUNCTIONS ========== init_html_report() { cat templates/report_header.html > "$HTML_REPORT" echo "

Oracle RAC Health Check Report

" >> "$HTML_REPORT" echo "

Generated on: $(date)

" >> "$HTML_REPORT" } close_html_report() { cat templates/report_footer.html >> "$HTML_REPORT" } append_section() { local title="$1" echo "

$title

" >> "$HTML_REPORT"
}

run_sql_file_report() {
    local title="$1"
    local sql_file="$2"
    append_section "$title"
    sqlplus -s "$DB_CONNECT_STRING" @"$sql_file" >> "$HTML_REPORT"
    echo "
" >> "$HTML_REPORT" } send_email_report() { if command -v mailx &>/dev/null; then cat "$HTML_REPORT" | mailx -a "Content-type: text/html" -s "$EMAIL_SUBJECT" "$EMAIL_TO" else echo "mailx not found. Please install or configure an alternative mail agent." fi } # ========== MAIN ========== main() { init_html_report for file in sql/*.sql; do title=$(basename "$file" .sql | sed 's/^[0-9]*_//; s/_/ /g' | awk '{print toupper(substr($0,1,1)) substr($0,2)}') run_sql_file_report "$title" "$file" done close_html_report send_email_report } main #!/bin/bash # ========== CONFIGURATION ========== ORACLE_USER="system" ORACLE_PASS="your_password" ORACLE_SID="ORCL" EMAIL_TO="dba-team@example.com" EMAIL_SUBJECT="Oracle RAC Health Check Report - $(date '+%Y-%m-%d %H:%M')" HTML_REPORT="./output/health_check_$(date +%Y%m%d_%H%M%S).html" DB_CONNECT_STRING="$ORACLE_USER/$ORACLE_PASS@$ORACLE_SID" # ========== ANALYSIS WINDOWS ========== DAYS_AGO=7 HOURS_AGO=1 MINUTES_AGO=5 # ========== THRESHOLDS ========== MAX_SESSIONS_UTIL=90 MAX_PROCESSES_UTIL=90 DB_LOAD_THRESHOLD=85 MAX_BLOCKED_SESSIONS=0 MAX_IO_RESP_MS=20 # ========== FUNCTIONS ========== init_html_report() { cat templates/report_header.html > "$HTML_REPORT" echo "

Oracle RAC Health Check Report

" >> "$HTML_REPORT" echo "

Generated on: $(date)

" >> "$HTML_REPORT" } close_html_report() { cat templates/report_footer.html >> "$HTML_REPORT" } append_section() { local title="$1" echo "

$title

" >> "$HTML_REPORT"
}

run_sql_file_report() {
    local title="$1"
    local sql_file="$2"
    append_section "$title"
    sqlplus -s "$DB_CONNECT_STRING" @"$sql_file" >> "$HTML_REPORT"
    echo "
" >> "$HTML_REPORT" } send_email_report() { if command -v mailx &>/dev/null; then cat "$HTML_REPORT" | mailx -a "Content-type: text/html" -s "$EMAIL_SUBJECT" "$EMAIL_TO" else echo "mailx not found. Please install or configure an alternative mail agent." fi } # ========== INDIVIDUAL CHECK FUNCTIONS ========== run_check_db_load() { run_sql_file_report "DB Load" "sql/01_db_load.sql"; } run_check_active_sessions() { run_sql_file_report "Active/Inactive Sessions" "sql/02_active_sessions.sql"; } run_check_session_processes() { run_sql_file_report "Session & Process Utilization" "sql/03_sessions_processes.sql"; } run_check_sga_pga_usage() { run_sql_file_report "SGA & PGA Usage" "sql/04_sga_pga_usage.sql"; } run_check_blocking_sessions() { run_sql_file_report "Blocking Sessions" "sql/05_blocking_sessions.sql"; } run_check_long_running_sessions() { run_sql_file_report "Long Running Sessions" "sql/06_long_running_sessions.sql"; } run_check_io_response_time() { run_sql_file_report "IO Response Time" "sql/07_io_response_time.sql"; } run_check_wait_events() { run_sql_file_report "Wait Events (1 Hour & 5 Min)" "sql/08_wait_events_window.sql"; } run_check_rac_instance_skew() { run_sql_file_report "RAC Instance Load Skew" "sql/09_rac_instance_skew.sql"; } run_check_top_waits() { run_sql_file_report "Top Wait Events by Instance" "sql/10_top_waits_by_instance.sql"; } run_check_global_cache() { run_sql_file_report "Global Cache Statistics" "sql/11_global_cache_stats.sql"; } run_check_top_sql() { run_sql_file_report "Top SQL by Elapsed Time" "sql/12_top_sql_elapsed.sql"; } run_check_tablespace_usage() { run_sql_file_report "Tablespace Usage" "sql/13_tablespace_usage.sql"; } run_check_asm_usage() { run_sql_file_report "ASM Diskgroup Usage" "sql/14_asm_diskgroup_usage.sql"; } run_check_log_sync() { run_sql_file_report "Redo Log Sync Waits" "sql/15_log_sync_contention.sql"; } run_check_temp_usage() { run_sql_file_report "Temp Usage" "sql/16_temp_usage.sql"; } run_check_parse_ratio() { run_sql_file_report "Parse to Execute Ratio" "sql/17_parse_to_exec_ratio.sql"; } run_check_log_switches() { run_sql_file_report "Log Switch History" "sql/18_log_switch_history.sql"; } run_check_fra_usage() { run_sql_file_report "FRA Space Usage" "sql/19_fra_usage.sql"; } run_check_ashtop_5min() { run_sql_file_report "ASH Top - Last 5 Min" "sql/20_ashtop_5min.sql"; } run_check_ashtop_1hr() { run_sql_file_report "ASH Top - Last 1 Hour" "sql/21_ashtop_1hr.sql"; } # ========== MAIN ========== main() { init_html_report run_check_db_load run_check_active_sessions run_check_session_processes run_check_sga_pga_usage run_check_blocking_sessions run_check_long_running_sessions run_check_io_response_time run_check_wait_events run_check_rac_instance_skew run_check_top_waits run_check_global_cache run_check_top_sql run_check_tablespace_usage run_check_asm_usage run_check_log_sync run_check_temp_usage run_check_parse_ratio run_check_log_switches run_check_fra_usage run_check_ashtop_5min run_check_ashtop_1hr close_html_report send_email_report } main rac_health_check.sh #!/bin/bash # ========== CONFIGURATION ========== ORACLE_USER="system" ORACLE_PASS="your_password" ORACLE_SID="ORCL" EMAIL_TO="dba-team@example.com" EMAIL_SUBJECT="Oracle RAC Health Check Report - $(date '+%Y-%m-%d %H:%M')" HTML_REPORT="/tmp/rac_health_check_report.html" DB_CONNECT_STRING="$ORACLE_USER/$ORACLE_PASS@$ORACLE_SID" # ========== INITIALIZE HTML ========== init_html_report() { echo "Oracle RAC Health Check" > "$HTML_REPORT" echo "

Oracle RAC Health Check Report

" >> "$HTML_REPORT" echo "

Generated on: $(date)

" >> "$HTML_REPORT" } close_html_report() { echo "" >> "$HTML_REPORT" } append_section() { local title="$1" echo "

$title

" >> "$HTML_REPORT" } run_sql_report() { local title="$1" local sql_block="$2" append_section "$title" echo "
" >> "$HTML_REPORT"
    echo "$sql_block" | sqlplus -s "$DB_CONNECT_STRING" >> "$HTML_REPORT"
    echo "
" >> "$HTML_REPORT" } # ========== SQL FUNCTIONS ========== sql_db_load_last_7_days() { cat <= SYSDATE - 7 ORDER BY begin_time; EOF } sql_active_inactive_sessions_last_7_days() { cat < SYSDATE - 7 GROUP BY TO_CHAR(sample_time, 'YYYY-MM-DD'), session_state ORDER BY 1, 2; EOF } sql_session_process_utilization() { cat < SYSDATE - 7 AND rownum <= 30; EOF } sql_blocking_sessions() { cat < SYSDATE - 1/12 GROUP BY event ORDER BY event_count DESC; SELECT event, COUNT(*) AS event_count FROM v\$active_session_history WHERE sample_time > SYSDATE - 1/288 GROUP BY event ORDER BY event_count DESC; EOF } # ========== EMAIL FUNCTION ========== send_email_report() { if command -v mailx &>/dev/null; then cat "$HTML_REPORT" | mailx -a "Content-type: text/html" -s "$EMAIL_SUBJECT" "$EMAIL_TO" else echo "mailx not found. Please install mailx or configure another mail agent." fi } # ========== MAIN ========== main() { init_html_report run_sql_report "1. DB Load (Last 7 Days)" "$(sql_db_load_last_7_days)" run_sql_report "2. Active/Inactive Sessions (Last 7 Days)" "$(sql_active_inactive_sessions_last_7_days)" run_sql_report "3. Session & Process Utilization" "$(sql_session_process_utilization)" run_sql_report "4. SGA & PGA Usage (Last 7 Days)" "$(sql_sga_pga_usage_7_days)" run_sql_report "5. Blocking Sessions" "$(sql_blocking_sessions)" run_sql_report "6. Long Running Sessions (>1 hour)" "$(sql_long_running_sessions)" run_sql_report "7. Current IO Response Time" "$(sql_io_response_time)" run_sql_report "8. Wait Events (Last 5 min & 1 hour)" "$(sql_wait_events_window)" close_html_report send_email_report } main #!/bin/bash # ========== CONFIGURATION ========== ORACLE_USER="system" ORACLE_PASS="your_password" ORACLE_SID="ORCL" EMAIL_TO="dba-team@example.com" EMAIL_SUBJECT="Oracle RAC Health Check Report - $(date '+%Y-%m-%d %H:%M')" HTML_REPORT="/tmp/rac_health_check_report.html" DB_CONNECT_STRING="$ORACLE_USER/$ORACLE_PASS@$ORACLE_SID" # ========== ANALYSIS WINDOWS ========== DAYS_AGO=7 HOURS_AGO=1 MINUTES_AGO=5 # ========== THRESHOLDS ========== MAX_SESSIONS_UTIL=90 # % MAX_PROCESSES_UTIL=90 # % DB_LOAD_THRESHOLD=85 # DB Load % MAX_BLOCKED_SESSIONS=0 MAX_IO_RESP_MS=20 # milliseconds sql_session_process_utilization() { cat < $MAX_SESSIONS_UTIL AND resource_name = 'sessions' THEN '⚠️ High Session Usage' WHEN ROUND((current_utilization/limit_value)*100,2) > $MAX_PROCESSES_UTIL AND resource_name = 'processes' THEN '⚠️ High Process Usage' ELSE 'OK' END AS status FROM v\$resource_limit WHERE resource_name IN ('sessions', 'processes'); EOF } sql_blocking_sessions() { cat < MAX_BLOCKED_SESSIONS )); then append_section "⚠️ ALERT: $BLOCK_COUNT Blocking Sessions Detected" fi } sql_rac_instance_skew() { cat < SYSDATE - $HOURS_AGO/24 GROUP BY inst_id, event ORDER BY inst_id, waits DESC FETCH FIRST 10 ROWS WITH TIES; EOF } sql_global_cache_stats() { cat < SYSDATE - $HOURS_AGO/24) ORDER BY elapsed_sec DESC) WHERE ROWNUM <= 10; EOF } sql_tablespace_usage() { cat < SYSDATE - $HOURS_AGO/24 GROUP BY inst_id, event ORDER BY wait_count DESC; EOF } sql_temp_usage() { cat < 0; EOF } sql_parse_to_exec_ratio() { cat < 100 ORDER BY parse_to_exec_pct DESC FETCH FIRST 10 ROWS WITH TIES; EOF } sql_log_switch_history() { cat <= SYSDATE - $DAYS_AGO GROUP BY TO_CHAR(first_time, 'YYYY-MM-DD HH24') ORDER BY hour; EOF } sql_fra_usage() { cat <