Oracle performance tool: WW (wait watcher)
Date: Fri, 14 Oct 1994 19:09:06 -0800
Message-ID: <jkennedy-1410941909060001_at_jkennedy-lap.us.oracle.com>
I had requests to post the Wait Watcher (WW) tool, so here it is. This message starts with an explanation of WW, the underlying V$ tables that support WW, some caveats, and finally the source code.
What is WW?
It is a tool (read: hack) to help you determine what Oracle sessions are waiting, what event they're waiting for, and how long they typically spend waiting for the each event.
Where does WW get its information?
WW simply displays information from the V$SESSION_WAIT, V$SESSION_EVENT, and V$SYSTEM_EVENT internal tables in Oracle. These tables contain information describing why the Oracle wait routine was called. These tables have been in the Oracle kernel since 7.0.12.
Note that time in the V$ tables mentioned above is returned as 100ths of seconds, while the time returned in WW is normalized to seconds.
What does each V$ table return?
V$SESSION_WAIT - latest or current reason a session is waiting V$SESSION_EVENT - for each session, a list of wait events and assoc'd info V$SYSTEM_EVENT - for the instance, a list of wait events and assoc'd info
What's a session? And an event? Any other definitions?
A session is a connect to Oracle. A single process can possibly have multiple connections (concurrent logons) to Oracle; each connection is a session. Each session has a unique session id or Sid.
There is a routine in Oracle used to perform a wait. When the wait routine is called by another part of Oracle code, a reason that the wait is being called is supplied to the routine for reporting in the V$ tables listed above. It doesn't affect the wait itself, but the information is interesting. This wait reason is also known as the event.
Additional information that has to do with the event is provided in WW's "Additional Information" column. Stuff you might see in here: if a foreground is waiting for a disk read to complete, the additional information provided will include the database file number and block number being read. That sort of stuff.
Every time the wait routine is called, the wait sequence number associated with the session is bumped. Why is this useful? You may find a session spending all of its time in the "client message" wait event (waiting for something to do from the user or client side) even though you know the client has done stuff (successfully, too). The problem isn't that V$SESSION_WAIT isn't seeing the other wait states, it's just that you can't query V$SESSION_WAIT fast enough to see any other state. You can tell this by watching the sequence number advance with every WW screen refresh.
Lastly, "Wait Time" is the amount of time spent doing the wait. This is pretty straightforward in the V$SESSION_EVENT and V$SYSTEM_EVENT views.
In the V$SESSION_WAIT view (the view that shows what's currently happening), "Wait Time" is a bit different. If the wait is currently happening, you'll see 0's or -1's for the wait time. If the time is some other number, then the session is not currently waiting, and the event and wait time listed reflect the statistics from the last time the wait routine was called. Wait happen so frequently that you may never see anything but active waits (0's or -1's in the wait time column).
OK...So show me some output from WW
When you run WW, you tell it which information you want to see: V$SESSION_WAIT, V$SESSION_EVENT or V$SYSTEM_EVENT. You specify which information and the screen update frequency on the command line.
By the way, while in the V$SESSION_WAIT view, WW will bold lines for sessions that have been active (left their previous wait state). Kind of cooling for seeing who's hot and who's not. The following listings don't show that bolding, but...
Here's some screen shots from each view followed by some comments to help you figure this stuff out:
V$SESSION_WAIT at 13:23:20.87 (time interval 1.00 seconds)
Sid Sequence Event Wait Time Additional Information
--- -------- ----------------- ----------- ---------------------- 1 112 pmon timer 0.00 ( 0, 0, 0) 2 206 rdbms ipc message 0.00 (timeout 300, 0, 0) 3 306 rdbms ipc message 0.00 (timeout 300, 0, 0) 4 112 rdbms ipc message 0.00 (timeout 300, 0, 0) 5 28 smon timer 0.00 (sleep time 300, failed 0, 0) 7 13 client message -1.00 (two-task? 0, driver id 19645580, 0) 9 20 client message -1.00 (two-task? 0, driver id 18683020, 0)
This view is best for seeing what's currently happening on your system. If you have a stuck Oracle process, you probably want to use this display to figure out why it's stuck. It may help if you determine the session id of the stuck process (with the other V$ tables) before you look at this view so you know which "Sid" to look for.
V$SESSION_EVENT at 13:25:35.45 (time interval 1.00 seconds)
Sid Event Total Waits Total Time Ave Wait --- ------------------------------ ----------- ----------- --------- 1 pmon timer 155 463.02 2.9872 2 db file sequential read 18 0.01 0.0006 2 control file sequential read 68 0.11 0.0016 2 db file parallel write 3 0.24 0.0800 2 rdbms ipc message 161 467.62 2.9045 3 log file sequential read 5 0.02 0.0040 3 db file sequential read 9 0.03 0.0033 3 control file sequential read 30 0.06 0.0020 3 latch free 1 0.08 0.0800 3 log file single write 5 0.12 0.0240 3 db file single write 9 0.20 0.0222 3 control file parallel write 24 0.50 0.0208 3 log file parallel write 59 0.76 0.0129 3 rdbms ipc message 208 466.26 2.2416 4 rdbms ipc message 155 465.12 3.0008 5 latch free 6 0.03 0.0050 5 db file scattered read 9 0.50 0.0556 5 db file sequential read 9 0.68 0.0756 5 smon timer 4 329.33 82.3325 7 client message 13 0.00 0.0000 9 client message 22 0.03 0.0014
The V$SESSION_EVENT view almost has too much information to be of use. If you have a particular session whose wait states you're interested in, it may be better just to run a SQL query in SQL*DBA against the V$SESSION_EVENT table using a "where" clause to find just that sessions wait states.
V$SYSTEM_EVENT at 13:26:42.95 (time interval 1.00 seconds)
Event Total Waits Total Time Ave Wait ------------------------------ ----------- ----------- --------- log file sync 1 0.04 0.0400 log file single write 6 0.14 0.0233 latch free 12 0.15 0.0125 log file sequential read 9 0.20 0.0222 db file parallel write 3 0.24 0.0800 control file sequential read 121 0.27 0.0022 db file scattered read 10 0.53 0.0530 db file single write 27 0.64 0.0237 log file parallel write 59 0.76 0.0129 control file parallel write 41 0.90 0.0220 client message 741 1.85 0.0025 db file sequential read 196 2.50 0.0128 rdbms ipc reply 7 2.79 0.3986 process startup 5 5.00 1.0000 smon timer 4 329.33 82.3325 pmon timer 179 532.75 2.9763 rdbms ipc message 592 1603.03 2.7078
The V$SYSTEM_EVENT is a great view for showing you how much time you spend doing particular things. For example, the above shows that we spend about 13 ms doing a log file write...not bad.
Caveats
First and most importantly...this is unsupported software. And it's not terribly robust either; for example, there's no built-in way to exit the program once you've started it. But WW gets the job done. So don't call me or anyone else at Oracle about WW. Of course, I'm interested in what you do with this and any suggestions (and code!) you have for improvements.
Next, it was written for Digital's VMS operating system. If you need it on some other O/S, you'll have to port it. But, there are only two VMS-specific calls...one to get the current time and another to wait (hibernate, sleep) for a specified amount of time. Porting isn't a big deal.
WW works by executing a SQL query against the appropriate V$ table, and sprintf's the output to a huge array, complete with ANSI escape sequences to control cursor positioning, erasing, and bolding of lines. Once the query results have all been returned, the array is printf'd to the screen. Why do this? Because it makes for fast, clean response time...much better than printf'ing a line at a time. Problem with this is that the V$SESSION_EVENT can return more info than will fit into the screen array, causing you to potentially overwrite other storage. That view has never been all that useful to me (recall my earlier comments about it), so I've never worried about it.
WW works best when running against a wide and tall screen...this keeps it from wrapping and scrolling.
The username/password to access the database is hard-coded into the program. While this isn't a problem for us that do benchmarking at Oracle, it is probably a problem for you. Best to externalize this to the command line.
The include files referenced in the source are standard VMS include files or standard Oracle include files. You should already have your own version of the Oracle files. You're on your own for the O/S-specific files.
Conclusion
Well, that's about it. Just so you know, this was compiled, linked, and verified to run before this was posted, so this stuff really, really does work. Flame the coding all you want, but realize this is something akin to my_first_C_program.C, and was written a while ago.
Good luck with this! I hope it proves useful to someone (otherwise I wouldn't have spent a Friday evening typing this in). I'd like to hear feedback!
Thanks.
Jef (jkennedy_at_oracle.com)
- 8<--------------------------------------------------------------------
#include <stdlib.h> /* makes atof() work correctly */ #include <stdio.h> #include <time.h> #include <timeb.h> #include <ssdef.h> /* VMS return statuses */ #include <descrip.h> /* makes $DESCRIPTOR work */ #include "ocidfn" /* gives us the struct for ORACLE things */ #include "oratypes" /* used by ocidfn */
/* ORACLE data structures & stuff */
typedef struct cda_def ldadef;
ldadef tpclda;
typedef struct cda_def csrdef;
csrdef *sqlcsr; char sqlbuf[1024];
#define V$SESSION_WAIT \
"select sid, seq#, nvl(event,' '), nvl(p1text,' '), p1, nvl(p2text,' '), \
p2, nvl(p3text,' '), p3, wait_time from v$session_wait order by sid"
#define V$SESSION_EVENT \
"select sid, nvl(event,' '), total_waits, time_waited, average_wait from\ v$session_event order by sid, time_waited"
#define V$SYSTEM_EVENT \
"select event, total_waits, time_waited, average_wait from v$system_event\ order by time_waited"
#define ROWS 200
int sid[ROWS]; /* columns for v$session_wait */ unsigned long seq[ROWS];
char event[ROWS][80]; char p1text[ROWS][80]; long p1[ROWS]; char p2text[ROWS][80]; long p2[ROWS]; char p3text[ROWS][80]; long p3[ROWS]; float wait_time[ROWS];
unsigned long total_waits[ROWS]; /* add'l columns for v$session_event */ unsigned long time_waited[ROWS]; /* ...and v$system_event */ float average_wait[ROWS];
char uid[80] = "sys/change_on_install"; ub4 err = 0; long status; /* return status from lib$ and sys$calls */
int x; float sleeptime = 1; /* sleep time */ int which = 1; /* default to session_wait */ char which_s[20];
$DESCRIPTOR(which_d,which_s);
typedef int quadword[2]; /* VMS time needs a quadword to store */ quadword time_bin; /* time of current sample, binary */ char time_asc[12]; /* ascii version of time to display */ $DESCRIPTOR(time_desc,time_asc); /* Descriptor for time_asc */ #define PVMAX 500 long prev_val[PVMAX]; char line_buffer[1000]; char screen_buffer[10000]; void usage(); void banner(); void errrpt();
/* Main Program */
main(argc, argv)
int argc;
char *argv[];
{
/* Handle the command line parameters */
if (argc >= 2)
{
strcpy(which_s,argv[1]); if (!(status = str$upcase(&which_d,&which_d))) exit(status); if (strcmp(which_s,"SESSION_WAIT") == 0) which = 1; else if (strcmp(which_s,"SESSION_EVENT") == 0) which = 2; else if (strcmp(which_s,"SYSTEM_EVENT") == 0) which = 3; else { printf("Can't determine the wait_type\n\n"); usage(); }
}
if (argc >= 3) sleeptime = (float) atoi(argv[2]) / 100;
if (argc >= 4) strcpy(uid,argv[3]);
/* Log on to ORACLE and open a cursor */ printf("Logging on to ORACLE...\n"); if (olon(&tpclda, uid, -1, (char *) -1, -1, 0)) errrpt(&tpclda);
printf("Opening cursor\n");
sqlcsr = (csrdef *)malloc(sizeof(csrdef));
if (oopen(sqlcsr,&tpclda, (text *)0, 0, -1, uid, -1)) errrpt(sqlcsr);
/* Do the right query */
switch (which) {
case 1: strcpy(sqlbuf,V$SESSION_WAIT); if (oparse(sqlcsr, sqlbuf, -1, 0, 2)) errrpt(sqlcsr); if (odefin(sqlcsr, 1, sid, sizeof(sid[0]), SQLT_INT, 0, (short *) 0, (char *) 0, 0, 0, (short *) 0, (short *) 0)) errrpt(sqlcsr); if (odefin(sqlcsr, 2, seq, sizeof(seq[0]), SQLT_INT, 0, (short *) 0, (char *) 0, 0, 0, (short *) 0, (short *) 0)) errrpt(sqlcsr); if (odefin(sqlcsr, 3, event, 80, SQLT_STR, 0, (short *) 0, (char *) 0, 0, 0, (short *) 0, (short *) 0)) errrpt(sqlcsr); if (odefin(sqlcsr, 4, p1text, 80, SQLT_STR, 0, (short *) 0, (char *) 0, 0, 0, (short *) 0, (short *) 0)) errrpt(sqlcsr); if (odefin(sqlcsr, 5, p1, sizeof(p1[0]), SQLT_INT, 0, (short *) 0, (char *) 0, 0, 0, (short *) 0, (short *) 0)) errrpt(sqlcsr); if (odefin(sqlcsr, 6, p2text, 80, SQLT_STR, 0, (short *) 0, (char *) 0, 0, 0, (short *) 0, (short *) 0)) errrpt(sqlcsr); if (odefin(sqlcsr, 7, p2, sizeof(p2[0]), SQLT_INT, 0, (short *) 0, (char *) 0, 0, 0, (short *) 0, (short *) 0)) errrpt(sqlcsr); if (odefin(sqlcsr, 8, p3text, 80, SQLT_STR, 0, (short *) 0, (char *) 0, 0, 0, (short *) 0, (short *) 0)) errrpt(sqlcsr); if (odefin(sqlcsr, 9, p3, sizeof(p3[0]), SQLT_INT, 0, (short *) 0, (char *) 0, 0, 0, (short *) 0, (short *) 0)) errrpt(sqlcsr); if (odefin(sqlcsr, 10, wait_time, sizeof(wait_time[0]), SQLT_FLT,0, (short *) 0, (char *) 0, 0, 0, (short *) 0, (short *) 0)) errrpt(sqlcsr);
strcpy(which_s,"V$SESSION_WAIT"); printf("\033[H\033[J"); while (1) { if (err = oexfet(sqlcsr,ROWS,0,0)) errrpt(sqlcsr); banner(); strcat(screen_buffer,"Sid Sequence Event Wait Time Additional Information\033[0K\n"); strcat(screen_buffer,"--- -------- ------------------------------ ----------- ----------------------\033[0K\n"); for (x = 0; x < sqlcsr->rpc; ++x) { if (wait_time[x] == (float) 4.2949673E+09) wait_time[x] = -100; if (prev_val[sid[x]] != seq[x]) { prev_val[sid[x]] = seq[x]; strcat(screen_buffer,"\033[1m"); } else { strcat(screen_buffer,"\033[0m"); } sprintf(line_buffer,"%3d %8d %-30s %11.2f (%s %d, %s %d, %s %d)\033[0K\n", sid[x], seq[x], event[x], (float) wait_time[x]/100, p1text[x], p1[x], p2text[x], p2[x], p3text[x], p3[x]); strcat(screen_buffer,line_buffer); } strcat(screen_buffer,"\033[0m\033[0J"); printf(screen_buffer); if ( status = lib$wait(&sleeptime) != SS$_NORMAL ) lib$stop(status); } case 2: strcpy(sqlbuf,V$SESSION_EVENT); if (oparse(sqlcsr, sqlbuf, -1, 0, 2)) errrpt(sqlcsr); if (odefin(sqlcsr, 1, sid, sizeof(sid[0]),SQLT_INT, 0, (short *) 0, (char *) 0, 0, 0, (short *) 0, (short *) 0)) errrpt(sqlcsr);
if (odefin(sqlcsr, 2, event, 80, SQLT_STR, 0, (short *) 0, (char *) 0, 0, 0, (short *) 0, (short *) 0)) errrpt(sqlcsr);
if (odefin(sqlcsr, 3, total_waits, sizeof(total_waits[0]), SQLT_INT, 0, (short *) 0, (char *) 0, 0, 0, (short *) 0, (short *) 0)) errrpt(sqlcsr);
if (odefin(sqlcsr, 4, time_waited, sizeof(time_waited[0]), SQLT_INT, 0, (short *) 0, (char *) 0, 0, 0, (short *) 0, (short *) 0)) errrpt(sqlcsr);
if (odefin(sqlcsr, 5, average_wait, sizeof(average_wait[0]),SQLT_FLT, 0, (short *) 0, (char *) 0, 0, 0, (short *) 0, (short *) 0)) errrpt(sqlcsr);
strcpy(which_s,"V$SESSION_EVENT"); printf("\033[H\033[J"); while (1) { if (err = oexfet(sqlcsr,ROWS,0,0)) errrpt(sqlcsr); banner(); strcat(screen_buffer,"Sid Event Total Waits Total Time Ave Wait\033[0K\n"); strcat(screen_buffer,"--- ----------------------------------------- ----------- ---------\033[0K\n");
for (x = 0; x < sqlcsr->rpc; ++x) { sprintf(line_buffer,"%3d %-30s %11d %11.2f %9.4f\033[0K\n", sid[x], event[x], total_waits[x], (float) time_waited[x]/100, (float) average_wait[x]/100); strcat(screen_buffer,line_buffer); } strcat(screen_buffer,"\033[0J"); printf(screen_buffer); if ( status = lib$wait(&sleeptime) != SS$_NORMAL ) lib$stop(status); } case 3: strcpy(sqlbuf,V$SYSTEM_EVENT); if (oparse(sqlcsr, sqlbuf, -1, 0, 2)) errrpt(sqlcsr); if (odefin(sqlcsr, 1, event, 80,SQLT_STR, 0, (short *) 0, (char *) 0, 0, 0, (short *) 0, (short *) 0)) errrpt(sqlcsr);
if (odefin(sqlcsr, 2, total_waits, sizeof(total_waits[0]), SQLT_INT, 0, (short *) 0, (char *) 0, 0, 0, (short *) 0, (short *) 0)) errrpt(sqlcsr);
if (odefin(sqlcsr, 3, time_waited, sizeof(time_waited[0]), SQLT_INT, 0, (short *) 0, (char *) 0, 0, 0, (short *) 0, (short *) 0)) errrpt(sqlcsr);
if (odefin(sqlcsr, 4, average_wait, sizeof(average_wait[0]),SQLT_FLT, 0, (short *) 0, (char *) 0, 0, 0, (short *) 0, (short *) 0)) errrpt(sqlcsr);
strcpy(which_s,"V$SYSTEM_EVENT"); printf("\033[H\033[J"); while (1) { if (err = oexfet(sqlcsr,ROWS,0,0)) errrpt(sqlcsr); banner(); strcat(screen_buffer,"Event Total Waits Total Time Ave Wait\033[0K\n"); strcat(screen_buffer,"----------------------------------------- ----------- ---------\033[0K\n");
for (x = 0; x < sqlcsr->rpc; ++x) { sprintf(line_buffer,"%-30s %11d %11.2f %9.4f\033[0K\n", event[x], total_waits[x], (float) time_waited[x]/100, (float) average_wait[x]/100); strcat(screen_buffer,line_buffer); } strcat(screen_buffer,"\033[0m\033[0J"); printf(screen_buffer); if ( status = lib$wait(&sleeptime) != SS$_NORMAL ) lib$stop(status); } /* end switch */
}
}
void errrpt(cur)
csrdef *cur;
{
text msg[2048];
if (cur->rc == 1403) return;
if (cur->rc) {
(void) ocierr(cur, cur->rc, msg, 2048); (void) printf("%s\n", msg);
}
exit(0);
}
void usage()
{
printf("Usage:\n"); printf(" $ WW [wait_type [interval [username/password]]]\n"); printf("\n"); printf(" wait_type = SESSION_WAIT (default) | SESSION_EVENT | SYSTEM_EVENT\n"); printf(" interval = 100's of seconds; defaults to 1 second (interval=100)\n"); printf(" username/password defaults to SYS/CHANGE_ON_INSTALL\n");
exit(0);
}
void banner()
{
/* Get the current time and convert it to ascii */ if (status = sys$gettim(&time_bin) != SS$_NORMAL) lib$stop(status); if (status = sys$asctim(0,&time_desc,&time_bin,1) != SS$_NORMAL) lib$stop(status);
sprintf(line_buffer,"\033[H%s at %s (time interval %6.2f seconds)\033[0K\n\033[0K\n",
which_s,time_asc,sleeptime);
screen_buffer[0]= '\0';
strcpy(screen_buffer,line_buffer);
}
/* end of file */
-- Jef "...I wouldn't normally do this kind of thing." -- Pet Shop BoysReceived on Sat Oct 15 1994 - 04:09:06 CET