In this Document
Purpose
Software Requirements/Prerequisites
Configuring the Script
Running the Script
Caution
Script
Script Output
References
This script will capture a 10046 trace for sessions of a particular user as soon as the session exists in the database and is detected by the script. This script was created to capture traces for very transient connections that where generated by a web application. This is an alternative to a logon trigger that is useful when a logon trigger is too risky to enable in a production environment.
Note: There is some risk in using this script! The script will turn on tracing for a specified number of users - if you trace too many users concurrently, there could be a performance impact. Also, if you kill the script after it has enabled traces, the sessions will continue tracing until they logoff (or you manually turn off the tracing).
Works with Oracle 7.3.4 and higher (tested up to 9.2). Requires SQLPlus.
Most of the functionality of this script is available in 10g and above with the DBMS_MONITOR package - please use that instead if possible.
1. Save the script listed here to a file (lets call it "tbu.sql" for the rest of this note).
2. Grant the following access privileges:
SELECT ANY TABLE, EXECUTE ANY PROCEDURE or specific grants on:
DBMS_LOCK, DBMS_SYSTEM, DBMS_OUTPUT, V_$SESSION, V_$PROCESS, V_$PARAMETER, V_$INSTANCE, V_$STATNAME, V_$SESSTAT
3. Execute the script within SQL*Plus to create the table, function, and procedure.
Once the script's objects are created, you can run this procedure by passing in the following parameters (in this order):
p_username - DB username to trace (can be upper or lowercase)
p_num_of_traces : total number of traces to generate
p_level - level of the 10046 trace (defaults to 8, trace sql and waitevents)
level 0 = same as sql trace
level 4 = same as 0 + bind variables
level 8 = same as 0 + wait events
level 12 = same as 0 + bind vars + wait events
p_sample_interval - how often to check if user logged off (default is 2 sec)
p_get_stats - TRUE/FALSE; collect session statistics for duration of trace
p_stickydelay - how long to wait for another session by same name to come along before timing out
p_misc_comment - some text comment
For example, to set username to TOBI, get two 10046 traces at level 8, set no delay in sampling, get stats, allow 30 secs
between sessions, and associate w/comment = 'AddEmp' :
SQL> set serveroutput on size 1000000
SQL> exec coe$trace_session_by_user('tobi', 2, 8, 0, TRUE, 30,'AddEmp')
Proofread this script before using it! Due to the differences in the way text editors, e-mail packages and operating systems handle text formatting (spaces, tabs and carriage returns), this script may not be in an executable state when you first receive it. Check over the script to ensure that errors of this type are corrected.
/* Name: coe$trace_session_by_user
Purpose: Wait for a user to logon then start a 10046 trace once user logs on...
keep tracing until user logs off.
Prequisites: SELECT ANY TABLE, EXECUTE ANY PROCEDURE or specific grants on:
DBMS_LOCK, DBMS_SYSTEM, DBMS_OUTPUT, V_$SESSION, V_$PROCESS, V_$PARAMETER, V_$INSTANCE, V_$STATNAME, V_$SESSTAT
!!!!!!!!!!!!!!!BE CAREFUL USING 10046 WITH MTS / SHARED SERVERS!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
By default, this script will trace only dedicated server sessions.
In some versions of the database, tracing shared server sessions could cause the entire
database to be running w/10046 turned on after a while. It's almost impossible to turn off tracing once this happens,
requiring the database to be shutdown and restarted.
If you are sure that it is OK to trace shared server sessions, you'll have to remove the restriction in the WHERE clause of
the query below ("server='DEDICATED'").
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Parameters:
p_username - DB username to trace (can be upper or lowercase)
p_num_of_traces : total number of traces to generate
p_level - level of the 10046 trace (defaults to 8, trace sql and waitevents)
level 0 = same as sql trace
level 4 = same as 0 + bind variables
level 8 = same as 0 + wait events
level 12 = same as 0 + bind vars + wait events
p_sample_interval - how often to check if user logged off (default is 2 sec)
p_get_stats - TRUE/FALSE; collect session statistics for duration of trace
p_stickydelay - how long to wait for another session by same name to come along before timing out
p_misc_comment - some text comment
Usage: From SQLPlus:
SQL> SET SERVEROUTPUT ON SIZE 1000000
SQL> exec coe$trace_session_by_user('scott')
To get 1 trace, set 10046 level 12 and wait for logoff at 10 second intervals:
SQL> exec coe$trace_session_by_user('scott', 1, 12, 10)
To set username to TOBI, get 2 X 10046 at level 8, set no delay in sampling, get stats, allow 30 secs
between sessions, and associate w/comment = 'AddEmp' :
SQL> exec coe$trace_session_by_user('tobi', 2, 8, 0, TRUE, 30,'AddEmp')
exec coe$trace_session_by_user('hpujol_us', 2, 12, 0, TRUE, 15,'home-1')
Then, look for the trace file in user_dump_dest corresponding to OS PID
Some useful queries:
select
ospid,cpu_overall, cpu_recursive, cpu_parse, cpu_other
from coe$trace_by_user_results
where misc_comment like 'start%'
select
a.trace_start, 'mlrep_ora_'||a.ospid, a.cpu_overall, a.misc_comment
from coe$trace_by_user_results a
where a.cpu_overall = (select max(b.cpu_overall)
from coe$trace_by_user_results b
where b.misc_comment like 'bug-srch2%')
and a.misc_comment like 'bug-srch2%';
select substr(misc_comment,1,length(misc_comment)-1) func, max(cpu_overall)
from coe$trace_by_user_results
where trace_start >= to_date('06-11-2001','mm-dd-yyyy')
group by substr(misc_comment,1,length(misc_comment)-1)
order by 2;
select distinct misc_comment
from coe$trace_by_user_results
where trace_start >= to_date('06-11-2001','mm-dd-yyyy')
select substr(a.mlf,1,length(a.mlf)-1) MLFunc,
sum(a.SumCPUOverall) / count(*) "Avg CPU Overall"
,sum(a.SumCPURec) / count(*) "Avg CPU Recursive"
,sum(a.SumCPUParse) / count(*) "Avg CPU Parse"
,sum(a.SumCPUOther) / count(*) "Avg CPU Other"
from (select misc_comment MLF
, sum(cpu_overall) SumCPUOverall
, sum(cpu_recursive) SumCPURec
, sum(cpu_parse) SumCPUParse
, sum(cpu_other) SumCPUOther
, count(*) Cnt
from coe$trace_by_user_results
where trace_start >= to_date('06-11-2001 08','mm-dd-yyyy hh24')
and username = 'SCOTT'
group by misc_comment ) a
group by substr(a.mlf,1,length(a.mlf)-1)
order by 2 desc;
select substr(misc_comment,1,length(misc_comment)-1) MLFunction
, avg(cpu_overall) "Avg CPU Overall"
, avg(cpu_recursive) "Avg CPU Rec"
, avg(cpu_other) "Avg CPU Other"
from coe$trace_by_user_results
where trace_start >= to_date('06-11-2001 08','mm-dd-yyyy hh24')
and username = 'SCOTT'
group by substr(misc_comment,1,length(misc_comment)-1)
order by 2 desc;
select misc_comment, ospid, cpu_overall
from coe$trace_by_user_results
where misc_comment like '&cmt%'
delete from coe$trace_by_user_results where misc_comment='bug-srch-keywrd-3';
exec coe$trace_session_by_user('SCOTT',2, 12,0,TRUE, 15,'tech_art-1')
------------------------------------------------------------------------------------------------------
*/
CREATE TABLE coe$trace_by_user_results (
timestamp_record DATE
,trace_start DATE
,trace_end DATE
,sid NUMBER
,serial# NUMBER
,username VARCHAR2(100)
,ospid VARCHAR2(10)
,program VARCHAR2(100)
,cpu_overall NUMBER
,cpu_recursive NUMBER
,cpu_parse NUMBER
,cpu_other NUMBER
,misc_comment VARCHAR2(200)
) TABLESPACE users;
CREATE OR REPLACE FUNCTION coe$trace_by_user_timeout ( p_username IN VARCHAR2
,p_level IN NUMBER DEFAULT 8
,p_sample_interval IN NUMBER DEFAULT 2
,p_get_stats IN BOOLEAN DEFAULT FALSE
,p_stickydelay IN NUMBER DEFAULT 60
,p_misc_comment IN VARCHAR2 DEFAULT NULL)
RETURN BOOLEAN
IS
TYPE sesStatType IS TABLE OF NUMBER
INDEX BY binary_integer;
BeginSesStat sesStatType;
EndSesStat sesStatType;
vCPUStatNo NUMBER;
vCPUBegin NUMBER := 0;
vCPUEnd NUMBER := 0;
vCPUDelta NUMBER := 0;
vRCPUStatNo NUMBER;
vRCPUBegin NUMBER := 0;
vRCPUEnd NUMBER := 0;
vRCPUDelta NUMBER := 0 ;
vPCPUStatNo NUMBER;
vPCPUBegin NUMBER := 0;
vPCPUEnd NUMBER := 0;
vPCPUDelta NUMBER := 0;
vOtherCPUDelta NUMBER := 0;
vGotStats BOOLEAN := FALSE;
vnSID v$session.sid%TYPE := -1;
vnSerial v$session.serial#%TYPE;
vnProgram v$session.program%TYPE;
vSPID v$process.spid%TYPE;
vPADDR v$session.paddr%TYPE;
vUDump v$parameter.value%TYPE;
VInstanceName v$instance.instance_name%TYPE;
vTraceFile_Name VARCHAR2(500);
vStartTime VARCHAR2(20);
vEndTime VARCHAR2(20);
vGotUser BOOLEAN := FALSE;
vKeepTracing BOOLEAN := TRUE;
vUsername v$session.username%TYPE := UPPER(p_username);
vErrNum NUMBER;
vErrMsg VARCHAR2(100);
vSampleStartTime DATE;
vStickyDelay NUMBER := p_stickydelay /(60*1440); -- secs -> fraction of a day to wait for another login by same name
BEGIN
DBMS_OUTPUT.PUT_LINE('IN coe$trace_by_user_timeout');
-- Init Stat Gathering
IF p_get_stats = TRUE THEN
SELECT statistic# INTO vCPUStatNo FROM v$statname WHERE name = 'CPU used by this session';
SELECT statistic# INTO vRCPUStatNo FROM v$statname WHERE name = 'recursive cpu usage';
SELECT statistic# INTO vPCPUStatNo FROM v$statname WHERE name = 'parse time cpu';
END IF;
vSampleStartTime := SYSDATE;
WHILE NOT vGotUser AND ( (SYSDATE - vSampleStartTime) <= vStickyDelay )
LOOP
FOR U in (select sid, serial#, paddr, program, sql_address, sql_hash_value
from v$session
where username=vUsername and server='DEDICATED')
LOOP
-- Caught the user, now we can exit
vGotUser := TRUE;
vnSID := U.sid;
vnSerial := U.serial#;
vPADDR := U.PADDR;
vnProgram := U.program;
-- Get Begin Stats
IF p_get_stats = TRUE THEN
BEGIN
SELECT value INTO BeginSesStat(vCPUStatNo) FROM v$sesstat WHERE sid = vnSID AND statistic# = vCPUStatNo;
SELECT value INTO BeginSesStat(vRCPUStatNo) FROM v$sesstat WHERE sid = vnSID AND statistic# = vRCPUStatNo;
SELECT value INTO BeginSesStat(vPCPUStatNo) FROM v$sesstat WHERE sid = vnSID AND statistic# = vPCPUStatNo;
DBMS_OUTPUT.PUT_LINE('Got Stats');
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Couldnt Get Begin Stats...too fast...keep looking for user');
vGotUser := FALSE;
END;
END IF;
END LOOP;
END LOOP;
-- Start Tracing the Session
IF vGotUser THEN
SYS.DBMS_SYSTEM.SET_EV(vnSID, vnSerial,10046,p_level,'');
BEGIN
SELECT spid INTO vSPID FROM v$process WHERE addr = vPADDR;
SELECT value INTO vUDump FROM v$parameter WHERE name = 'user_dump_dest';
SELECT LOWER(instance_name) INTO VInstanceName FROM v$instance;
vStartTime := TO_CHAR(sysdate, 'DD-MON-YYYY HH24:MI:SS');
vTraceFile_Name := VUDump||'/'||VInstanceName||'_ora_'||vSPID||'.trc';
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Couldnt get base info');
END;
DBMS_OUTPUT.PUT_LINE('--------- Session Info -----------');
DBMS_OUTPUT.PUT_LINE('SID: '||TO_CHAR(vnSID));
DBMS_OUTPUT.PUT_LINE('Serial: '||TO_CHAR(vnSerial));
DBMS_OUTPUT.PUT_LINE('OS PID: '||vSPID);
DBMS_OUTPUT.PUT_LINE('Program: '||vnProgram);
DBMS_OUTPUT.PUT_LINE('----------------------------------');
DBMS_OUTPUT.PUT_LINE('Started Trace of user '||UPPER(p_username)||' at '||vStartTime);
-- Loop around until the user is gone
WHILE vKeepTracing
LOOP
vKeepTracing := FALSE;
FOR U in (select sid, serial# from v$session where sid=vnSID AND serial#=vnSerial)
LOOP
IF U.SID = vnSID AND U.SERIAL# = vnSerial THEN
vKeepTracing := TRUE;
IF p_get_stats = TRUE THEN
-- Get latest stats
BEGIN
SELECT value INTO EndSesStat(vCPUStatNo) FROM v$sesstat WHERE sid = vnSID AND statistic# = vCPUStatNo;
SELECT value INTO EndSesStat(vRCPUStatNo) FROM v$sesstat WHERE sid = vnSID AND statistic# = vRCPUStatNo;
SELECT value INTO EndSesStat(vPCPUStatNo) FROM v$sesstat WHERE sid = vnSID AND statistic# = vPCPUStatNo;
vGotStats := TRUE;
EXCEPTION
WHEN NO_DATA_FOUND THEN
vKeepTracing := FALSE;
DBMS_OUTPUT.PUT_LINE('Couldnt Get stat update.');
DBMS_OUTPUT.PUT_LINE('User '||vUsername||' has logged off.');
EXIT;
END;
END IF;
END IF;
IF p_sample_interval > 0 THEN
DBMS_LOCK.SLEEP(p_sample_interval);
END IF;
END LOOP;
END LOOP;
-- Stop the Trace
SYS.DBMS_SYSTEM.SET_EV(vnSID, vnSerial,10046,0,'');
vEndTime := TO_CHAR(sysdate, 'DD-MON-YYYY HH24:MI:SS');
DBMS_OUTPUT.PUT_LINE('Stopped Tracing at '||vEndTime);
DBMS_OUTPUT.PUT_LINE(' ');
DBMS_OUTPUT.PUT_LINE('Trace file: '||vTraceFile_Name);
-- Print out Session Stats collected
IF vGotStats = TRUE THEN
vCPUDelta := EndSesStat(vCPUStatNo) - BeginSesStat(vCPUStatNo);
vRCPUDelta := EndSesStat(vRCPUStatNo) - BeginSesStat(vRCPUStatNo);
vPCPUDelta := EndSesStat(vPCPUStatNo) - BeginSesStat(vPCPUStatNo);
vOtherCPUDelta := vCPUDelta - vRCPUDelta - vPCPUDelta;
DBMS_OUTPUT.PUT_LINE('--------- Session CPU Statistics -----------');
DBMS_OUTPUT.PUT_LINE('Delta Overall CPU = '||TO_CHAR(vCPUDelta/100)||' sec');
DBMS_OUTPUT.PUT_LINE('Delta Recursive CPU = '||TO_CHAR(vRCPUDelta/100)||' sec');
DBMS_OUTPUT.PUT_LINE('Delta Parse CPU = '||TO_CHAR(vPCPUDelta/100)||' sec');
DBMS_OUTPUT.PUT_LINE('Delta Other CPU = '||TO_CHAR(vOtherCPUDelta/100)||' sec');
ELSE
DBMS_OUTPUT.PUT_LINE('Couldnt Get End Stats');
END IF;
-- Put results into table
INSERT INTO coe$trace_by_user_results
(
timestamp_record
,trace_start
,trace_end
,sid
,serial#
,username
,ospid
,program
,cpu_overall
,cpu_recursive
,cpu_parse
,cpu_other
,misc_comment
)
VALUES
(
SYSDATE
,TO_DATE(vStartTime,'DD-MON-YYYY HH24:MI:SS')
,TO_DATE(vEndTime, 'DD-MON-YYYY HH24:MI:SS')
,vnSID
,vnSerial
,vUsername
,vSPID
,vnProgram
,vCPUDelta/100
,vRCPUDelta/100
,vPCPUDelta/100
,vOtherCPUDelta/100
,p_misc_comment
);
COMMIT;
RETURN TRUE; -- all done, success
ELSE -- Timeout waiting for user to login
DBMS_OUTPUT.PUT_LINE('Timed out waiting for user '||vUsername);
RETURN FALSE;
END IF;
DBMS_OUTPUT.PUT_LINE('Leaving... coe$trace_session_by_user');
EXCEPTION
WHEN OTHERS THEN
vErrNum := SQLCODE;
vErrMsg := SUBSTR(SQLERRM, 1, 100);
DBMS_OUTPUT.PUT_LINE('ERROR = '||TO_CHAR(vErrNum));
DBMS_OUTPUT.PUT_LINE('ERROR MSG = '||vErrMsg);
RETURN FALSE;
END;
/
show errors
CREATE OR REPLACE PROCEDURE coe$trace_session_by_user ( p_username IN VARCHAR2
,p_num_of_traces IN NUMBER DEFAULT 3
,p_level IN NUMBER DEFAULT 8
,p_sample_interval IN NUMBER DEFAULT 2
,p_get_stats IN BOOLEAN DEFAULT FALSE
,p_stickydelay IN NUMBER DEFAULT 60
,p_misc_comment IN VARCHAR2 DEFAULT NULL)
IS
vTraceFileCount NUMBER := 0;
vSuccessful BOOLEAN := TRUE;
BEGIN
DBMS_OUTPUT.ENABLE(1000000);
DBMS_OUTPUT.PUT_LINE('IN coe$trace_session_by_user');
LOOP
DBMS_OUTPUT.PUT_LINE('*************************************************');
vSuccessful := coe$trace_by_user_timeout(p_username, p_level, p_sample_interval, p_get_stats, p_stickydelay, p_misc_comment);
IF vSuccessful THEN
vTraceFileCount := vTraceFileCount + 1;
END IF;
EXIT WHEN vTraceFileCount = p_num_of_traces;
EXIT WHEN NOT vSuccessful;
END LOOP;
DBMS_OUTPUT.PUT_LINE('Successfully Traced '||TO_CHAR(vTraceFileCount)||' sessions.');
END;
/
show errors
SQL> exec coe$trace_session_by_user('SYSTEM',2, 12,1, true,10,'test2');
IN coe$trace_session_by_user
*************************************************
IN coe$trace_by_user_timeout
Got Stats
--------- Session Info -----------
SID: 10
Serial: 650
OS PID: 10391
Program: sqlplus@coehq2 (TNS V1-V3)
----------------------------------
Started Trace of user SYSTEM at 30-MAY-2006 17:15:17
Stopped Tracing at 30-MAY-2006 17:15:41
Trace file: /u01/app/oracle/product/DB9iR2/admin/DB9iR2/udump/db9ir2_ora_10391.trc
--------- Session CPU Statistics -----------
Delta Overall CPU = 1.48 sec
Delta Recursive CPU = .05 sec
Delta Parse CPU = .06 sec
Delta Other CPU = 1.37 sec
*************************************************
IN coe$trace_by_user_timeout
Got Stats
--------- Session Info -----------
SID: 10
Serial: 653
OS PID: 10395
Program: sqlplus@coehq2 (TNS V1-V3)
----------------------------------
Started Trace of user SYSTEM at 30-MAY-2006 17:15:49
Stopped Tracing at 30-MAY-2006 17:16:07
Trace file: /u01/app/oracle/product/DB9iR2/admin/DB9iR2/udump/db9ir2_ora_10395.trc
--------- Session CPU Statistics -----------
Delta Overall CPU = 1.38 sec
Delta Recursive CPU = 0 sec
Delta Parse CPU = 0 sec
Delta Other CPU = 1.38 sec
Successfully Traced 2 sessions.
PL/SQL procedure successfully completed.