IBM Books

System Monitor Guide and Reference


Programming to Read an Event Monitor Trace

Each record in the binary event monitor stream, except for the log header, starts with the size and type of the record. While reading the trace, it is extremely important that the size of a record is used for skipping a record in the trace, both to ensure that your application will be able to handle the traces produced by future releases of DB2, and because byte padding is sometimes used in the output stream. WARNING: You should never use sizeof() on event monitor records. Similarly, the type of every record should be checked. Skipping unknown or unwanted records will allow your application to handle any event monitor trace.

The log header describes the characteristics of the trace, containing information such as the memory model (for example big endian) of the server where the trace was collected, and the codepage of the database. You may have to do byte swapping on numerical values, if the system where you read the trace has a different memory model than the server (for example, Windows NT to UNIX). Codepage translation may also need to be done, if the database is configured in a different language than the machine from which you read the trace.

The following annotated fragments from the code samples in sqllib/samples/c_AIX illustrates the most important considerations in programming to read an event monitor trace. Some error handling is omitted, for simplicity. This code should run on all platforms, except for PIPE I/O routines (however, more platforms are addressed in the actual code samples).

Reading the Data Stream

The following routines illustrate how you can open, read, or skip bytes from a PIPE or FILE on a UNIX platform.

//------------------------------------------------------------------------------
// File functions  - Using the ANSI C library
//------------------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
//------------------------------------------------------------------------------
FILE* openFile(char *file_name) {
   return fopen(file_name,"rb"); /* returns NULL if file cannot be opened */
}
//------------------------------------------------------------------------------
int closeFile(FILE* handle) {
  return fclose(handle);
}
//------------------------------------------------------------------------------
int readFromFile(char* buffer, int size, FILE* fp) {
   int rc=0;                            // returns 0 (success); EOF; or errno
   int records_read = fread(buffer, size, 1, fp);
   if (records_read != 1) {
      if (feof(fp))
         rc = EOF;
      else rc = errno;
   } /* end if no data was returned */
   return rc;
} /* end readFromFile */
 
//------------------------------------------------------------------------------
// Pipe functions  -  for AIX
//------------------------------------------------------------------------------
#include <unistd.h>    /* for pipe functions on AIX */
#include <fcntl.h>     /* for definition of O_RDONLY and open() */
//------------------------------------------------------------------------------
int openNamedPipe (char   *pipe_name) {
   return open(pipe_name, O_RDONLY);
}
//------------------------------------------------------------------------------
int closeNamedPipe (int handle) {
  return close(handle);
}
//------------------------------------------------------------------------------
int readFromPipe(int handle, char* buffer, int size) {
   int rc=0;
   int num_bytes;
   num_bytes = read(handle, buffer, size);
   if (num_bytes != size) {
      if (num_bytes==0)
         rc=EOF;
      else rc = num_bytes;
   } /* end did not get the expected number of bytes back from read() */
   return rc;
} /* end readFromPipe */
 
//------------------------------------------------------------------------------
// Read data from Event Monitor trace (FILE or PIPE) returns 0 (success) or EOF
//------------------------------------------------------------------------------
int read_data(EventLog* evtlog,
              char*     buffer,
              int       size) {
   int rc=0;
   if (evtlog->type == EVMFile) {
      rc = readFromFile(buffer, size, evtlog->current_fp);
      if (rc && rc!=EOF) {
          fprintf(stderr, "ERROR: Could not read from: %s\n",
                       evtlog->current_fn);
          exit(1);
      } /* end cannot read the log header from the file */
   } /* end if the Event Monitor Log is read from a file */
   else {
      rc = readFromPipe(evtlog->handle, buffer, size);
      if (rc && rc!=EOF) {
         fprintf(stderr, "ERROR: Could not read a data from: %s\n",
                          evtlog->target);
         exit(2);
      } /* end cannot read from the pipe */
   } /* end else the Event Log is read from a pipe */
   return rc;
} /* end of read_data */
 
//------------------------------------------------------------------------------
// Skip n bytes from current position in the trace
//------------------------------------------------------------------------------
void skip_data(EventLog* evtlog, int n) {
   if (evtlog->type == EVMFile)
      fseek(evtlog->current_fp, n, SEEK_CUR);
   else if (evtlog->type == EVMPipe) {
      lseek(evtlog->handle, n, SEEK_CUR);
   } /* end else pipe event monitor */
} /* end skip_data */

Swapping Bytes in Numerical Values

This code is required when transferring data between systems using different conventions for storing numerical values (for example, UNIX to Windows NT).

#include <sqlmon.h>   // DB2 Database Monitor interface
//------------------------------------------------------------------------------
// Byte conversion macros
//------------------------------------------------------------------------------
#define SWAP2(s) ((((s) >> 8) & 0xFF) | (((s) << 8) & 0xFF00))
 
#define SWAP4(l) ((((l) >> 24) & 0xFF) | ((((l) & 0xFF0000) >> 8) & 0xFF00) \
                      | (((l) & 0xFF00) << 8) | ((l) << 24))
 
//------------------------------------------------------------------------------
void swapBytes_sqlm_event_log_header(sqlm_event_log_header* r) {
   r->size =                       SWAP4(r->size);
   r->version =                    SWAP4(r->version);
   r->codepage_id =                SWAP2(r->codepage_id);
   r->country_code =               SWAP2(r->country_code);
} // end of swapBytes_sqlm_event_log_header)
 
   // . . .

Reading the Event Records

After the log header has been read, all remaining records in the trace follow the following layout. Each record starts and terminates on a 4 byte boundary; and records are never split across 2 files.




* Figure SQLF0120 not displayed.

The size accounts for both header and data.

//------------------------------------------------------------------------------
// Read an event record  - returns:  0 (success) or EOF
//------------------------------------------------------------------------------
int read_event_record(EventLog *evtlog, char *buffer, int *event_type) {
 
   sqlm_event_rec_header*  pHeader = (sqlm_event_rec_header*) buffer;
 
   //---------------------------------------------------------------------------
   // Read the record header
   //---------------------------------------------------------------------------
   int rc;
   rc=read_data(evtlog, (char *) pHeader, pHeader->size);
   if (rc)
      return rc;  /* could be at EOF */
 
   *event_type   = pHeader->type; // The event type is specified in the header
 
   if (evtlog->needByteReversal)
      swapBytes_sqlm_event_rec_header(pHeader);
 
   //---------------------------------------------------------------------------
   // Read the rest of the data
   //---------------------------------------------------------------------------
   rc=read_data(evtlog, buffer        + pHeader->size,
                        pHeader->size - pHeader->size);
 
   if (rc==0 && evtlog->needByteReversal)
      swapBytes(pHeader->type, buffer);
 
   return rc;
} /* end of read_event_record */

Reading the Log Header

You must take care of byte reversal, and possibly code page conversion. This example only handles byte reversal.

// From sqlmon.h, the DB2 system monitor header file:
// LOG HEADER
typedef struct sqlm_event_log_header
{
  int            byte_order;                   /* Big Endian or Little Endian */
  unsigned long  size;                         /* Size of this record         */
  unsigned long  version;                      /* Event Monitor Version       */
  char           event_monitor_name[SQLM_IDENT_SZ];  /* Name of the Event Mon */
  unsigned short codepage_id;                  /* Code page of Database       */
  unsigned short country_code;                 /* Country Code of Database    */
  char           server_prdid[SQLM_IDENT_SZ];  /* Server Product Id           */
  char           server_instance_name[SQLM_IDENT_SZ]; /*instance name of DB2  */
  unsigned long  number_nodes_in_system;
}sqlm_event_log_header;
 
//------------------------------------------------------------------------------
// Attributes of an Event Log
// This structure is used to keep all information that is required to
// process the output of any Event Monitor (file or pipe).
//------------------------------------------------------------------------------
typedef enum   EventMonitorType { EVMPipe, EVMFile } EventMonitorType;
typedef struct EventLog {
   sqlm_event_log_header   header;
   char*                   target;
   Boolean                 needByteReversal; // True if running on a machine
                                             // with a different memory model
   EventMonitorType        type;             // type: file or pipe
   int                     handle;           // For Named pipe only
   FILE*                   current_fp;       // File currently open
   char                    current_fn[512];  // It's name.
} EventLog;
 
//------------------------------------------------------------------------------
// Read the log header - store its attributes into an 'EventLog' structure.
//------------------------------------------------------------------------------
#ifdef _AIX    // Compiler pre-defined macro on AIX
#define BIG_ENDIAN_MEMORY
#else
// Assume an INTEL platform
#define LITTLE_ENDIAN_MEMORY
#endif
void read_log_header( /* output */ EventLog* evtlog) {
 
   // Read the Event Trace header
   read_data(evtlog,                        // input: event trace (or log)
            (char*) &evtlog->header,        // output: log header
            sizeof(sqlm_event_log_header)); // input: number of bytes to read
                                            //        is what we can handle
   // Check if the memory model is different and we need byte-reveral
   switch (evtlog->header.byte_order) {
      case SQLM_BIG_ENDIAN:
      #ifdef LITTLE_ENDIAN_MEMORY
         evtlog->needByteReversal = 1;
      #else
         evtlog->needByteReversal = 0;
      #endif
         break;
      case SQLM_LITTLE_ENDIAN:
      #ifndef LITTLE_ENDIAN_MEMORY
         evtlog->needByteReversal = 1;
      #else
         evtlog->needByteReversal = 0;
      #endif
         break;
   } // end switch
 
   // Convert the header, if the server had a different memory model than ours
   if (evtlog->needByteReversal)
      swapBytes_sqlm_event_log_header(&evtlog->header);
 
   // Skip extra bytes, if the record is bigger than what we can handle
   // (which may become the case in subsequent releases of the product)
   if (evtlog->header.size > sizeof(sqlm_event_log_header)) {
      skip_data(evtlog, evtlog->header.size - sizeof(sqlm_event_log_header));
   } // end if more bytes than expected for this record in the log file
} // end read_log_header

Printing Event Records

All timestamps in event monitor records are GMT time since January 1, 1970.

All strings in event monitor records are padded with blanks, up to their maximum size. Strings are NEVER NULL terminated.

The following routines illustrate one method of handling blank-padded strings and converting GMT time into local time. It also shows how to print any event monitor.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>  /* To use cprint() function */
#include <time.h>
#include <sqlmon.h>   // DB2 Database Monitor interface
 
//------------------------------------------------------------------------------
// Convert GMT time into local time, and format it into a printable string.
//------------------------------------------------------------------------------
char* time_STRING(const sqlm_timestamp timestamp, char *timeString) {
 
   // Event Monitor returns GMT time, adjust it to local time
   struct tm *pstTm;
   pstTm = localtime((signed long*) &timestamp.seconds);
   if (timestamp.seconds == 0)
      strcpy(timeString, "");
   else {
      if (timestamp.microsec < 0) {
         sprintf(timeString, "%02d-%02d-%04d %02d:%02d:%02d",
              pstTm->tm_mon + 1, pstTm->tm_mday, pstTm->tm_year + 1900,
              pstTm->tm_hour, pstTm->tm_min, pstTm->tm_sec);
      } else {
         sprintf(timeString, "%02d-%02d-%04d %02d:%02d:%02d.%06.6ld",
              pstTm->tm_mon + 1, pstTm->tm_mday, pstTm->tm_year + 1900,
              pstTm->tm_hour, pstTm->tm_min, pstTm->tm_sec, timestamp.microsec);
     } /* end else micro seconds are not null */
  } /* end if the timestamp is non-zero */
  return timeString;
} // end of time_STRING
 
 All strings in  an event monitor traces are BLANK PADDED up to their maximum
 size.  They *are not* null terminated.
 
//------------------------------------------------------------------------------
// Print a Blank Padded String of maximum length SZ
//------------------------------------------------------------------------------
// note: strings returned by DB2 are NOT NULL-TERMINATED, they are all
//       blank padded up to some maximum length.
//------------------------------------------------------------------------------
// For example, given:
//                   char   str[20] = "Contents of 1       ";
// the following invocation:
//                   fpBPSTR(stdout, " String 1", str, sizeof(str));
// will print:
//                   " String 1: Contents of 1"
//------------------------------------------------------------------------------
#define fpBPSTR(fp, txt, str, SZ) \
{ \
    char newstr[SZ]; \
    int k=0; \
    while (str[k]!=' '&&k<SZ) { newstr[k]=str[k]; k++;} \
    if (k<SZ) newstr[k]='\0'; \
    fprintf(fp, txt ": %0.*s\n", SZ, newstr); \
}
 
//------------------------------------------------------------------------------
char* byte_order_STRING(int val) {
   switch (val) {
   case SQLM_LITTLE_ENDIAN:      return "SQLM_LITTLE_ENDIAN";
   case SQLM_BIG_ENDIAN:         return "SQLM_BIG_ENDIAN";
   }
   return "";
} // end of byte_order_STRING
 
//------------------------------------------------------------------------------
// Print the log header record
//------------------------------------------------------------------------------
void print_sqlm_event_log_header(FILE* fp,
                                 const sqlm_event_log_header *event_header){
   fprintf(fp,
"--------------------------------------------------------------------------\n"
"                            EVENT LOG HEADER\n");
   fpBPSTR(fp, "  Event Monitor name",
                  event_header->event_monitor_name, SQLM_IDENT_SZ);
   fpBPSTR(fp, "  Server Product ID",
                  event_header->server_prdid, SQLM_IDENT_SZ);
   fprintf(fp, "  Version of event monitor data: %ld\n", event_header->version);
   fprintf(fp, "  Byte order: %s\n",
                  byte_order_STRING(event_header->byte_order));
   fprintf(fp, "  Size of record: %ld\n", event_header->size);
   fprintf(fp, "  Codepage of database: %d\n", event_header->codepage_id);
   fprintf(fp, "  Country code of database: %d\n", event_header->country_code);
   fpBPSTR(fp, "  Server instance name",
                  event_header->server_instance_name, SQLM_IDENT_SZ);
   fprintf(fp,
"--------------------------------------------------------------------------\n");
   fflush(fp);
} /* end of print_sqlm_event_log_header */
 
//------------------------------------------------------------------------------
// Print an event record
//------------------------------------------------------------------------------
void print_event_record(FILE* fp, int event_type, char* rec, int rec_no) {
   switch (event_type) {
   case SQLM_EVENT_DB:
//      print_sqlm_db_event(fp, (const sqlm_db_event*) rec, rec_no);
      break;
   case SQLM_EVENT_CONN:
//      print_sqlm_conn_event(fp, (sqlm_conn_event*) rec, rec_no);
      break;
   case SQLM_EVENT_TABLE:
//      print_sqlm_table_event(fp, (sqlm_table_event*) rec, rec_no);
      break;
   case SQLM_EVENT_STMT:
//      print_sqlm_stmt_event(fp, (sqlm_stmt_event*) rec, rec_no);
      break;
   case SQLM_EVENT_STMTTEXT:
//      print_sqlm_stmttext_event(fp, (sqlm_stmttext_event*) rec, rec_no);
      break;
   case SQLM_EVENT_XACT:
//      print_sqlm_xaction_event(fp, (sqlm_xaction_event*) rec, rec_no);
      break;
   case SQLM_EVENT_DEADLOCK:
//      print_sqlm_deadlock_event(fp, (sqlm_deadlock_event*) rec, rec_no);
      break;
   case SQLM_EVENT_DLCONN:
//      print_sqlm_dlconn_event(fp, (sqlm_dlconn_event*) rec, rec_no);
      break;
   case SQLM_EVENT_TABLESPACE:
//      print_sqlm_tablespace_event(fp, (sqlm_tablespace_event*) rec, rec_no);
      break;
   case SQLM_EVENT_DBHEADER:
//      print_sqlm_dbheader_event(fp, (sqlm_dbheader_event*) rec, rec_no);
      break;
   case SQLM_EVENT_CONNHEADER:
//      print_sqlm_connheader_event(fp, (sqlm_connheader_event*) rec, rec_no);
      break;
   case SQLM_EVENT_OVERFLOW:
//      print_sqlm_overflow_event(fp, (sqlm_overflow_event*) rec, rec_no);
      break;
   case SQLM_EVENT_START:
//      print_sqlm_evmon_start_event(fp,(sqlm_evmon_start_event*) rec, rec_no);
      break;
   default:
//      print_unknown_event(fp, rec, rec_no);
      break;
   } /* end switch on event type */
   fflush (fp);
} /* end of print_event_record */

Reading Events from a FILE Trace

This sample illustrates how to handle multiple files.

A file event monitor writes to files that are created in the directory identified by its target. When initially turned on, it starts writing to file: 00000000.evt, when this file is full (as specified by the MAXFILESIZE parameter on create event monitor), it moves on to file: 00000001.evt, and so on.

//------------------------------------------------------------------------------
// Build a fully qualified Event Monitor file name, given a file number
//------------------------------------------------------------------------------
#if _AIX
   #define PATH_SEP     '/'
   #define PATH_SEP_STR "/"
#else /* Assume Intel platform */
   #define PATH_SEP     '\\'
   #define PATH_SEP_STR "\\"
#endif
void build_event_monitor_file_name(const char* target, int fnum, char *fn) {
 
   // Build the full filename (path + filename)
   int len = strlen(target);
   if (target[len-1] == PATH_SEP) {
      sprintf(fn, "%s%0.8d.evt", target, fnum);
   } else {
      sprintf(fn, "%s%s%0.8d.evt", target, PATH_SEP_STR, fnum);
   } // end else need to append path delimiter to directory name
} /* end of build_event_monitor_file_name */
 
//------------------------------------------------------------------------------
// Read Events from files
//------------------------------------------------------------------------------
void read_events_from_file(char* target) {
   EventLog                evtlog;       /* Attributes for this event log     */
   int record_no;                          /* current record number */
   int file_no;                            /* current file number */
   int rc=0;
   int end_of_trace=0;                   /* True when no more files to read   */
   char buffer[4096];                    /* buffer for reading Event records  */
 
   //---------------------------------------------------------------------------
   // Initialize the attributes of this Event Log
   //---------------------------------------------------------------------------
   evtlog.type   = EVMFile;   // A File log
   evtlog.target = target;    // Directory where the files reside
 
   //---------------------------------------------------------------------------
   // Open the first file and read the log header
   //---------------------------------------------------------------------------
   file_no=0;                           /* First file to open is 00000000.evt */
   build_event_monitor_file_name(evtlog.target, file_no, evtlog.current_fn);
   read_log_header(&evtlog);
   print_sqlm_event_log_header(stdout, &evtlog.header);  // Print it
 
   //---------------------------------------------------------------------------
   // Read/print events from the trace file(s)
   //---------------------------------------------------------------------------
   record_no=0;                          /* Number of event records processed */
   while (!end_of_trace) {
      int event_type;                    /* Type of event read from the trace */
      rc=read_event_record(&evtlog, buffer, &event_type);
      if (rc == EOF) {
 
         /* Try to open the next trace file, if any */
         closeFile(evtlog.current_fp);
         build_event_monitor_file_name(evtlog.target,
                                       ++file_no, evtlog.current_fn);
         if ((evtlog.current_fp = openFile(evtlog.current_fn))==NULL)
            end_of_trace=true;      /* No more files to read */
         else {
            rc=read_event_record(&evtlog, buffer, &event_type);
            if (rc==EOF)
               end_of_trace = true;
         } /* else read the event from the next file */
      } else if (rc) end_of_trace = true;
 
      if (rc==0) {
         // Process the event
         print_event_record(stdout, event_type, buffer, ++record_no);
      } /* end if we got an event record */
   } /* end while there are more files in the Event Monitor trace */
} // end of read_events_from_file

See evm.c sample application in engn/samples/c_AIX for the complete source.


[ Top of Page | Previous Page | Next Page | Table of Contents | Index ]

[ DB2 List of Books | Search the DB2 Books ]