blob: 8cf82ac5657cd3d64708b15396eaba8c91d67118 [file] [log] [blame]
// ***************************************************************************
// @@@ START COPYRIGHT @@@
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
// @@@ END COPYRIGHT @@@
// -*-C++-*-
// ***************************************************************************
//
// File: CmpErrLog.cpp
// Description: This file contains the implementation of the CmpErrLog
// class. It is used to log the transient properties that were
// in affect when certain errors occur. This is meant solely to
// make it easier for support personnel and SQL developers to
// tell what was going on when errors occur. The need for
// logging resulted from error 2008 problems, but this logging
// may be useful for other reasons too.
//
// When an object of this class is constructed, this class
// will log information about the system and process memory
// statistics, the heap that caused the error, the context heap,
// the statement heap, the CQDs that were in affect, the query
// that was being executed, and a stack trace of the current
// process.
//
// This class has no outside functions that need to be called.
// Simply constructing a class will cause the logging to occur.
// For example, this class may be used as follows:
//
// if (errOccurred) {
// CmpErrLog("Memory allocation failure", heapPtr, size);
// }
//
// Created: 9/19/2008
// Language: C++
//
// ***************************************************************************
#include "Platform.h"
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>
#include <execinfo.h>
#include <sys/file.h>
#include "CmpErrLog.h"
#include "CmpCommon.h"
#include "CmpContext.h"
#include "CmpStatement.h"
#include "SchemaDB.h"
#include "Analyzer.h"
#include "vproc.h"
#define MAX_LOGFILE_SIZE (1 * 1024 * 1024) // One megabyte
#define MAX_LOGFILE_NAME_LEN 125
#define INITIAL_RESERVE_MEM_SIZE 2048
#define SUBSEQUENT_MEM_RESERVE_SIZE 1024
// Allocate some memory globally that can be freed to allow the
// CmpErrLog class to work. Without any available memory, it could
// be difficult to open files or walk the stack. Windows, in
// particular, needs plenty of memory to lookup symbols.
void *CmpErrLog::memPtr = malloc(INITIAL_RESERVE_MEM_SIZE);
// CmpErrLog::renameBigLogFile() will resize the old log file if
// it is larger than MAX_LOGFILE_SIZE. This function checks the
// current size of the file.
void CmpErrLog::renameBigLogFile(const char *fileName)
{
struct stat sbuf;
if (stat(fileName, &sbuf) != 0)
return;
if (sbuf.st_size >= MAX_LOGFILE_SIZE)
{
// Create the .old filename string
char oldFileName[MAX_LOGFILE_NAME_LEN + 5];
strncpy(oldFileName, fileName, sizeof(oldFileName));
strncat(oldFileName, ".old", sizeof(oldFileName));
// Rename the old file name.
rename(fileName, oldFileName);
}
}
// CmpErrLog::openLogFile() opens the logfile specified by the
// CMP_ERR_LOG_FILE CQD and may lock the file to prevent concurrent
// compiler processes from trying to write to the file.
void CmpErrLog::openLogFile()
{
CmpContext *context = CmpCommon::context();
if (context == NULL)
return;
// Lookup the CMP_ERR_LOG_FILE CQD.
const char *logFileName;
logFileName = context->schemaDB_->getDefaults().getValue(CMP_ERR_LOG_FILE);
// If the logfile is empty or is too long, then don't try to open it.
// This code tries to not allocate any memory that isn't absolutely
// needed and it makes assumptions as to the maximum pathname.
if (!logFileName
|| logFileName[0] == '\0'
|| strlen(logFileName) > MAX_LOGFILE_NAME_LEN)
return;
// On Linux determine the log location based off of TRAF_HOME environment
// variable.
char logFilePath[MAX_LOGFILE_NAME_LEN+1];
char *sqroot = getenv("TRAF_HOME");
// Use a generated path for the logfile if 3 conditions are met:
// 1. TRAF_HOME is set in the environment.
// 2. The first character of the logfile starts with a character or digit.
// 3. The complete path can fit into the size of the "logFilePath" buffer.
if ((sqroot != NULL) &&
(isalnum(logFileName[0])) &&
(strlen(sqroot) + strlen(logFileName) < sizeof(logFilePath) - 7))
{
// Create the full path to the log file in the logFilePath buffer.
snprintf(logFilePath, sizeof(logFilePath), "%s/logs/%s",
sqroot, logFileName);
// Call renameBigLogFile() to check the size and rename the file
// if it is too big.
renameBigLogFile(logFilePath);
// Open the logfile.
fp = fopen(logFilePath, "a");
}
else
{
renameBigLogFile(logFileName);
fp = fopen(logFileName, "a");
}
// If the file could not be opened, then return.
if (fp == NULL)
return;
// Change permissions so anybody can write to the file.
(void)fchmod(fileno(fp), 0666);
flock(fileno(fp), LOCK_EX);
// Now that we are ensured to have exclusive access, seek to the
// end for the case where another MXCMP may have just appended
// to the file.
(void)fseek(fp, 0, SEEK_END);
}
// CmpErrLog::closeLogFile() unlocks and closes the log file.
void CmpErrLog::closeLogFile()
{
if (fp != NULL)
{
fflush(fp);
flock(fileno(fp), LOCK_UN);
fclose(fp);
fp = NULL;
}
// Attempt to reserve memory in case this class is needed for a
// subsequent memory log.
if (memPtr == NULL)
memPtr = malloc(SUBSEQUENT_MEM_RESERVE_SIZE);
}
// Two macros to help with displaying of vproc string.
#define DefToStr2(a) #a
#define DefToStr(a) "" DefToStr2(a) ""
// CmpErrLog::writeHeader() writes to the log file information
// about the time and a little information about the current
// process.
void CmpErrLog::writeHeader(const char *failureTxt, CollHeap *failedHeap, size_t size)
{
fprintf(fp, "--------------------------------------------------------\n");
// Save the local time to the log file.
time_t curTime = time(NULL);
fprintf(fp, "%s\n", ctime(&curTime));
// Save the failure text.
fprintf(fp, "%s\n", failureTxt);
// If a heap was passed in or the size is not equal to zero, then
// save information about the heap and size of the allocation.
if (failedHeap != NULL)
{
if (size != 0)
fprintf(fp, "Failed while allocating " PFSZ " bytes from the heap at %p.\n\n",
size, failedHeap);
else
fprintf(fp, "Failed while allocating from the heap at %p.\n\n",
failedHeap);
}
else if (size != 0)
{
fprintf(fp, "Failed while allocating " PFSZ " bytes.\n", size);
}
// Write vproc string to the log file
char vprocBuffer[80];;
strcpy(vprocBuffer, DefToStr(PRODNUMCMP));
strcat(vprocBuffer, "_");
strcat(vprocBuffer, DefToStr(DATE1CMP));
strcat(vprocBuffer, "_");
strcat(vprocBuffer, DefToStr(CMP_CC_LABEL));
fprintf(fp, "Software version: %s\n\n", vprocBuffer);
}
// CmpErrLog::writeMemoryStats() writes to the log file information
// about the available system memory and also any process statistics
// that are available.
void CmpErrLog::writeMemoryStats()
{
fprintf(fp, "System Memory Statistics:\n");
Int32 meminfoFD;
ssize_t numBytes;
char buf[256];
if ((meminfoFD = open("/proc/meminfo", O_RDONLY)) == -1)
{
fprintf(fp, "Unable to open /proc/meminfo: %s\n\n", strerror(errno));
}
else
{
// Copy /proc/meminfo to the logfile.
while ((numBytes = read(meminfoFD, buf, sizeof(buf))) > 0)
fwrite(buf, sizeof(char), numBytes, fp);
fputc('\n', fp);
close(meminfoFD);
}
fprintf(fp, "Process Statistics:\n");
// Copy /proc/<pid>/status to the log file.
char procStatusFile[40];
snprintf(procStatusFile, sizeof(procStatusFile), "/proc/%d/status", getpid());
Int32 procStatusFD;
if ((procStatusFD = open(procStatusFile, O_RDONLY)) == -1)
{
fprintf(fp, "Unable to open %s: %s\n\n", procStatusFile, strerror(errno));
}
else
{
// The copying of the file takes place in this loop.
while ((numBytes = read(procStatusFD, buf, sizeof(buf))) > 0)
fwrite(buf, sizeof(char), numBytes, fp);
fputc('\n', fp);
close(procStatusFD);
}
}
// CmpErrLog::writeHeapInfo() writes to the log file information
// about a single heap.
void CmpErrLog::writeHeapInfo(CollHeap *heap)
{
if (heap == NULL)
return;
fprintf(fp, "Heap : %p\n", heap);
fprintf(fp, "Name : %s\n", heap->getName());
fprintf(fp, "Total Size : " PFSZ "\n", heap->getTotalSize());
fprintf(fp, "Alloc Size : " PFSZ "\n", heap->getAllocSize());
fprintf(fp, "Highwater Mark : " PFSZ "\n\n", heap->getHighWaterMark());
}
// CmpErrLog::writeHeapInfo() writes to the log file information
// about the heap passed in and about the context heap and
// statement heap (if they are different than the current heap).
void CmpErrLog::writeAllHeapInfo(CollHeap *failedHeap)
{
fprintf(fp, "Heap Information:\n");
if (failedHeap != NULL)
writeHeapInfo(failedHeap);
CmpContext *context = CmpCommon::context();
// Write the context heap information
CollHeap *contextHeap = context->heap();
if (contextHeap != NULL && contextHeap != failedHeap)
writeHeapInfo(contextHeap);
// Write the statement heap information
CollHeap *statementHeap = context->statementHeap();
if (statementHeap != NULL && statementHeap != failedHeap)
writeHeapInfo(statementHeap);
}
// CmpErrLog::writeCQDInfo() writes any modified CQDs to the
// log file.
void CmpErrLog::writeCQDInfo()
{
fprintf(fp, "Control Query Defaults:\n");
NADefaults &defs = CmpCommon::context()->schemaDB_->getDefaults();
for (UInt32 i = 0; i < defs.numDefaultAttributes(); i++)
{
const char *attrName = defs.lookupAttrName(i);
DefaultConstants attrEnum = NADefaults::lookupAttrName(attrName);
const char *val;
switch(defs.getProvenance(attrEnum))
{
case NADefaults::DERIVED:
case NADefaults::READ_FROM_SQL_TABLE:
case NADefaults::COMPUTED:
case NADefaults::SET_BY_CQD:
val=defs.getValue(i);
fprintf(fp, "CONTROL QUERY DEFAULT %s '%s';\n", attrName, val);
break;
case NADefaults::UNINITIALIZED:
case NADefaults::INIT_DEFAULT_DEFAULTS:
case NADefaults::IMMUTABLE:
default:
// Don't write this CQD to the output file.
break;
}
}
fputc('\n', fp);
}
// CmpErrLog::writeQueryInfo() writes information about the current
// query to the log file.
void CmpErrLog::writeQueryInfo()
{
CmpContext *context = CmpCommon::context();
CmpStatement *statement = context->statement();
if (statement == NULL)
{
fprintf(fp, "There is no current statement\n\n");
return;
}
fprintf(fp, "Statement Information:\n");
// The task count is logged when the compiler has
// reached at least the optimization stage. The
// task_count_fmt points to the format string that is
// used.
const char *task_count_fmt = "Task Count : %d\n";
QueryAnalysis *qa = QueryAnalysis::Instance();
if (qa != NULL)
{
fprintf(fp, "Compiler Phase : ");
switch (qa->getCompilerPhase())
{
case QueryAnalysis::PRE_BINDER:
fprintf(fp, "PRE_BINDER\n");
break;
case QueryAnalysis::BINDER:
fprintf(fp, "BINDER\n");
break;
case QueryAnalysis::NORMALIZER:
fprintf(fp, "NORMALIZER\n");
break;
case QueryAnalysis::ANALYZER:
fprintf(fp, "ANALYZER\n");
break;
case QueryAnalysis::OPTIMIZER:
fprintf(fp, "OPTIMIZER\n");
fprintf(fp, task_count_fmt, CURRSTMT_OPTDEFAULTS->getTaskCount());
break;
case QueryAnalysis::PRECODE_GENERATOR:
fprintf(fp, "PRECODE_GENERATOR\n");
fprintf(fp, task_count_fmt, CURRSTMT_OPTDEFAULTS->getTaskCount());
break;
case QueryAnalysis::GENERATOR:
fprintf(fp, "GENERATOR\n");
fprintf(fp, task_count_fmt, CURRSTMT_OPTDEFAULTS->getTaskCount());
break;
case QueryAnalysis::POST_GENERATOR:
fprintf(fp, "POST_GENERATOR\n");
break;
default:
fprintf(fp, "UNKNOWN: CompilerPhaseEnum=%d\n",
qa->getCompilerPhase());
break;
}
}
fputc('\n', fp);
if (statement->userSqlText() != NULL)
fprintf(fp, "SQL Text:\n%s\n\n", statement->userSqlText());
}
// CmpErrLog::writeQueryInfo() writes the stack trace of the
// compiler to the log file.
void CmpErrLog::writeStackTrace()
{
fprintf(fp, "Process Stack Trace:\n");
// This is a quick and dirty implementation for Linux. It is easy to
// get the program counters for the stack trace, but is difficult to
// look up the function name, line, and file number based off of the
// program counter. For simplicity, this code just calls addr2line to
// look up the information. This could be changed in the future if an
// easy to use API becomes available.
void *bt[20];
size_t size = backtrace(bt, 20);
pid_t myPID = getpid();
// Write each level of the stack except for the top frame and the
// bottom two frames, which aren't important here.
Int32 i = 1;
while (i < size - 2)
{
char buffer[128]; // Used for command-line + addr2line output.
sprintf(buffer, "/usr/bin/addr2line -e /proc/%d/exe -f -C ", myPID);
Int32 j;
// Run addr2line on 5 addresses at a time.
for (j = i; j < i+5 && j < size-2; j++)
{
char addrBuf[12];
sprintf(addrBuf, " %p", bt[j]);
strcat(buffer, addrBuf);
}
FILE *cmdFP = popen(buffer, "r");
if (cmdFP == NULL)
{
fprintf(fp, "Error %d while popen() of %s\n", errno, buffer);
break;
}
else
{
for (j = i; j < i+5 && j < size-2; j++)
{
// Read from the addr2line output
fgets(buffer, sizeof(buffer), cmdFP);
// Replace newline with null character
size_t len = strlen(buffer);
if (buffer[len-1] == '\n')
buffer[len-1] = '\0';
fprintf(fp, "%p: %s()\n", bt[j], buffer);
fgets(buffer, sizeof(buffer), cmdFP);
fprintf(fp, " %s", buffer);
}
fclose(cmdFP);
}
i = j;
}
fputc('\n', fp);
}
// CmpErrLog::CmpErrLogCallback() is called by the NAMemory code when
// a memory allocation failure occurs. This function just logs the
// failure.
void CmpErrLog::CmpErrLogCallback(NAHeap *heap, size_t userSize)
{
CmpErrLog("Memory allocation failure", heap, userSize);
}
// CmpErrLog::CmpErrLog() constructs a CmpErrLog object and
// contains all of the calls that are necessary for logging
// information about the current state of the compiler.
CmpErrLog::CmpErrLog(const char *failureTxt, CollHeap *failedHeap, size_t size)
: fp(0)
{
// If memory is allocated, then free it to give some extra memory
// for logging.
if (memPtr != NULL)
{
free(memPtr);
memPtr = NULL;
}
// Create and lock the log file.
openLogFile();
// If the log file was opened successfully, then write the
// information to the log file.
if (fp != NULL)
{
writeHeader(failureTxt, failedHeap, size);
writeMemoryStats();
writeAllHeapInfo(failedHeap);
writeCQDInfo();
writeQueryInfo();
writeStackTrace();
closeLogFile();
}
}
// CmpErrLog::~CmpErrLog() is the destructor and frees any resources
// used by the CmpErrLog class.
CmpErrLog::~CmpErrLog()
{
// Call closeLogFile() to make sure the log file is closed. This
// could potentially happen if an exception is thrown.
closeLogFile();
}