blob: 8e18b2e9340896f1a37a8b4a10289c2ffcbeebe9 [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: hs_log.C
* Description: Utilities for logging, diagnostics handling.
* Created: 03/25/96
* Language: C++
*
*
*
*
*****************************************************************************
*/
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#define HS_FILE "hs_log"
#include "CmpCommon.h"
#include "CmpErrLog.h"
#include "SchemaDB.h"
#include "NAError.h"
#include "cli_stdh.h"
#include "sql_id.h"
#include "hs_cont.h"
#include "hs_globals.h"
#include "SQLCLIdev.h"
#include "ComDiags.h"
#include "ComCextdecs.h"
#include "NAClusterInfo.h"
#include "QRLogger.h"
#define ULOG_PATH_WARN 2244
// -----------------------------------------------------------------------
// Fill diagnostic area:
// - merge in CLI diagnostic info if needed.
// - add in our own diagnostic info.
// -----------------------------------------------------------------------
void HSFuncMergeDiags( Lng32 sqlcode
, const char *string0
, const char *string1
, NABoolean needCLIDiags
, Lng32 fsError
)
{
HSLogMan *LM = HSLogMan::Instance();
HSGlobalsClass *hs_globals = GetHSContext();
if (hs_globals == NULL) return;
if (hs_globals->requestedByCompiler) return; // Do not create errors if quick stats.
ComDiagsArea &diagsArea = hs_globals->diagsArea;
Lng32 sqlcode2 = 0;
ComDiagsArea diags(STMTHEAP);
if (needCLIDiags)
{
SQL_EXEC_MergeDiagnostics_Internal(diags); // copy out diags area.
//Make sure all 6007 and 6008 warnings are removed from DiagsArea. We do
//not want user to know about these warnings while executing Update
//Statistics.
ComCondition *wEntry;
for(Lng32 i = 1; i <= diags.getNumber(DgSqlCode::WARNING_) ; i++)
{
wEntry = diags.getWarningEntry(i);
if (wEntry->getSQLCODE() == 6007 ||
wEntry->getSQLCODE() == 6008)
{
diags.deleteWarning(--i);
}
}
sqlcode2 = diags.mainSQLCODE();
}
diagsArea << DgSqlCode(sqlcode);
if (hs_globals->objDef)
{
diagsArea << DgCatalogName(hs_globals->objDef->getCatName().data());
diagsArea << DgSchemaName(hs_globals->objDef->getSchemaName().data());
diagsArea << DgTableName(hs_globals->objDef->getObjectName().data());
}
if (string0 != NULL)
{
diagsArea << DgString0(string0);
// Inaccessible object error uses TableName field as parameter value, but
// it is not in hs_globals when we issue this error, so we have to use
// the name passed to this fn as string0.
if (sqlcode == -UERR_OBJECT_INACCESSIBLE)
diagsArea << DgTableName(string0);
}
if (string1 != NULL)
diagsArea << DgString1(string1);
// If an internal error was triggered by some other error, add a parameter
// that specifies that error number. The format of the message is such that
// it will read correctly with or without this parameter.
if (sqlcode == -UERR_INTERNAL_ERROR)
{
if (sqlcode2 < 0)
{
char errNumParam[10];
snprintf(errNumParam, sizeof(errNumParam), " (%d)", -sqlcode2);
diagsArea << DgString2(errNumParam);
}
else
diagsArea << DgString2("");
}
if (fsError)
diagsArea << DgInt0(fsError);
if (needCLIDiags && sqlcode2)
diagsArea.mergeAfter(diags);
CmpCommon::diags()->mergeAfter(diagsArea);
if (LM->LogNeeded())
{
snprintf(LM->msg, sizeof(LM->msg), "*** MERGEDIAGS: SQLCODE=%d", sqlcode );
LM->Log(LM->msg);
if (string0 != NULL)
{
snprintf(LM->msg, sizeof(LM->msg), ", TOK1=%s", string0);
LM->Log(LM->msg);
}
if (string1 != NULL)
{
snprintf(LM->msg, sizeof(LM->msg), ", TOK2=%s", string1);
LM->Log(LM->msg);
}
if (sqlcode2)
{
snprintf(LM->msg, sizeof(LM->msg), ", SQLCODE2=%d", sqlcode2);
LM->Log(LM->msg);
}
}
}
// -----------------------------------------------------------------------
// Clean up log as determined by the current log settings
void HSCleanUpLog()
{
HSLogMan *LM = HSLogMan::Instance();
if (LM->GetLogSetting() == HSLogMan::SYSTEM)
{
NABoolean internalError = CmpCommon::diags()->contains(-UERR_INTERNAL_ERROR);
NABoolean dropError = CmpCommon::diags()->contains(-UERR_UNABLE_TO_DROP_OBJECT);
NABoolean descError = CmpCommon::diags()->contains(-UERR_UNABLE_TO_DESCRIBE_COLUMN_NAMES);
NABoolean createError = CmpCommon::diags()->contains(-UERR_UNABLE_TO_CREATE_OBJECT);
NABoolean internalError1 = CmpCommon::diags()->contains(-UERR_GENERIC_ERROR);
NABoolean bwError = CmpCommon::diags()->contains(-UERR_UNEXPECTED_BACKWARDS_DATA);
if (internalError || dropError || descError || createError ||
internalError1 || bwError)
LM->StopLog(); // stop logging but keep the log
else
LM->StopAndDeleteLog(); // stop logging and throw the log away
}
}
// -----------------------------------------------------------------------
// Log the location of error.
// All non-zero returns will invoke this procedure, including warnings,
// which are denoted by positive integers. If we handle an error here and
// logging is not enabled, we activate Just-In-Time logging (subject to
// CQD setting) dump as much information as possible about the error.
// -----------------------------------------------------------------------
void HSFuncLogError(Lng32 error, char *filename, Lng32 lineno)
{
HSGlobalsClass *hs_globals = GetHSContext();
HSLogMan *LM = HSLogMan::Instance();
// put this fn call in all error routines
if (error != 100)
NAError_stub_for_breakpoints() ;
if (LM->LogNeeded())
{
if (error < 0)
snprintf(LM->msg, sizeof(LM->msg), "*** ERROR[%d] in %s:%d\n", error, filename, lineno);
else
snprintf(LM->msg, sizeof(LM->msg), "*** WARNING[%d] in %s:%d\n", error, filename, lineno);
LM->Log(LM->msg);
}
else if (error < 0 && CmpCommon::getDefault(USTAT_JIT_LOGGING) == DF_ON)
{
// Just-in-time logging is enabled; write critical info to log.
LM->StartLog(FALSE);
LM->Log("#####>> Update Statistics Just-In-Time logging activated");
snprintf(LM->msg, sizeof(LM->msg), "*** ERROR[%d] in %s:%d", error, filename, lineno);
LM->Log(LM->msg);
LM->LogTimestamp("Error occurred");
if (hs_globals)
{
// This function is called from HSHandleError, which may be invoked
// more than once for a single error as the stack is unwound. The
// information from HSGlobalsClass is only needed in the log once
// whether or not subsequent errors are distinct from the first one.
// For errors after the first, we append a warning that it may be a
// duplicate of one already seen.
Int32 offset = snprintf(LM->msg, sizeof(LM->msg), "Error number %d for this statement",
hs_globals->errorCount+1);
if (hs_globals->errorCount > 0)
snprintf(LM->msg + offset, sizeof(LM->msg)-offset, " (may be duplicate posting)");
LM->Log(LM->msg);
if (hs_globals->errorCount == 0)
hs_globals->log(LM); // Add information from HSGlobalsClass
}
// Log this error to the file indicated by CMP_ERR_LOG_FILE CQD.
// first construct the message
snprintf(LM->msg, sizeof(LM->msg), "Failure in ustat code: Error %d in %s:%d", error, filename, lineno);
CmpErrLog(LM->msg);
LM->Log("#####>> Recorded this failure in CMP_ERR_LOG_FILE \n");
LM->Log("#####>> End of JIT log entry\n");
LM->StopLog();
}
// If not a warning, bump global error count and set file and line if first one.
if (hs_globals && error < 0)
{
if (hs_globals->errorCount == 0)
{
hs_globals->errorFile = filename;
hs_globals->errorLine = lineno;
}
hs_globals->errorCount++;
}
}
/*****************************************************************************/
/* CLASS: HSLogMan */
/* PURPOSE: Log manager for all of Update Statistics needs. */
/* DEPENDENCY: */
/* NOTES: This is a singleton class, which means that there could only be */
/* one instance of this class. */
/*****************************************************************************/
THREAD_P HSLogMan* HSLogMan::instance_ = 0;
HSLogMan::HSLogMan()
: logSetting_(OFF), currentTimingEvent_(-1), logNeeded_(FALSE)
{
memset(startTime_, 0, MAX_TIMING_EVENTS*sizeof(Int64));
logFile_ = new (CTXTHEAP) NAString();
prevTime_.tv_sec = prevTime_.tv_usec = 0;
if (CmpCommon::getDefault(USTAT_AUTOMATIC_LOGGING) == DF_ON)
logSetting_ = SYSTEM;
}
/***********************************************/
/* METHOD: Instance() */
/* PURPOSE: Returns the instance of the class. */
/* NOTES: We need to instantiate using the */
/* contextHeap because we need this */
/* class to stay around through */
/* multiple statements. */
/***********************************************/
HSLogMan* HSLogMan::Instance()
{
if (instance_ == 0)
instance_ = new (GetCliGlobals()->exCollHeap()) HSLogMan;
return instance_;
}
/***********************************************/
/* METHOD: Log() */
/* PURPOSE: Write input parameter contents to */
/* log. */
/* INPUT: data - data string to be written */
/***********************************************/
void HSLogMan::Log(const char *data)
{
if (logNeeded_)
{
QRLogger::log(CAT_SQL_USTAT, LL_INFO, "%s", data);
}
}
//
// METHOD: SetLogSetting()
// PURPOSE: Change the current logging state
// INPUT: logSetting - Indicates the new logging state
//
void HSLogMan::SetLogSetting(LogSetting logSetting)
{
switch (logSetting)
{
case ON:
StartLog();
break;
case SYSTEM:
if (logSetting_ == ON) // if logging was ON
StopLog(); // stop existing log so it doesn't get deleted
StartLog();
break;
case OFF:
if (logSetting_ == SYSTEM) // if logging was SYSTEM
StopAndDeleteLog(); // stop and delete log
else
StopLog();
break;
default:
break; // should not occur; just ignore
}
logSetting_ = logSetting;
}
/***********************************************/
/* METHOD: StartLog() */
/* PURPOSE: Begin capturing log information */
/* INPUT: needExplain - Indicator of whether */
/* to turn on cqd */
/* to get explain data for dynamic */
/* queries. TRUE except for just-in-*/
/* time logging, where we want to */
/* avoid clearing dx area. */
/* logFileName - If NULL (default), a */
/* cqd determines log file name. */
/* NOTES: It is possible for the USTAT_LOG */
/* attribute to be modified many */
/* times but will not be official */
/* until either StartLog() or */
/* ClearLog() methods are called. */
/***********************************************/
void HSLogMan::StartLog(NABoolean needExplain)
{
if (!logNeeded_) // if logging isn't already on
{
// Construct logfile name incorporating process id and node number. Note that
// the 2nd parameter of processhandle_decompose is named cpu but is actually
// the node number for Seaquest (the 4th param, named nodenumber, is the cluster
// number).
Int32 nodeNum;
Int32 pin;
SB_Phandle_Type procHandle;
XPROCESSHANDLE_GETMINE_(&procHandle);
XPROCESSHANDLE_DECOMPOSE_(&procHandle, &nodeNum, &pin);
long currentTime = (long)time(0);
const size_t MAX_FILENAME_SIZE = 50;
char qualifiers[MAX_FILENAME_SIZE];
sprintf(qualifiers, ".%d.%d.%ld.txt", nodeNum, pin, currentTime);
std::string logFileName;
QRLogger::getRootLogDirectory(CAT_SQL_USTAT, logFileName /* out */);
if (logFileName.size() > 0)
logFileName += '/';
logFileName += "ustat"; // file name prefix will always be "ustat"
// if CQD USTAT_LOG is set, extract the file name part from it and insert
// that to the log file name as an additional qualifier
const char * ustatLog = ActiveSchemaDB()->getDefaults().getValue(USTAT_LOG);
if (strlen(ustatLog) > 0)
{
const char * fileNameQualifier = ustatLog + strlen(ustatLog);
// strip off any directory path name; we will always use the logs directory
// as configured via QRLogger
while ((fileNameQualifier > ustatLog) && (*(fileNameQualifier - 1) != '/'))
fileNameQualifier--;
logFileName += '.';
logFileName += fileNameQualifier;
}
logFileName += qualifiers;
NABoolean logStarted = QRLogger::startLogFile(CAT_SQL_USTAT,logFileName.c_str());
if (logStarted)
{
*logFile_ = logFileName.c_str();
currentTimingEvent_ = -1;
startTime_[0] = 0; /* reset timer */
logNeeded_ = TRUE;
// The GENERATE_EXPLAIN cqd captures explain data pertaining to dynamic
// queries. Ordinarily we want it on, but for just-in-time logging triggered
// by an error, we don't need it, and can't set it because HSFuncExecQuery
// clears the diagnostics area, which causes the error to be lost.
explainOn_ = needExplain;
if (needExplain)
{
if (HSFuncExecQuery("CONTROL QUERY DEFAULT GENERATE_EXPLAIN 'ON'") != 0)
explainOn_ = FALSE; // couldn't turn it on (shouldn't happen)
}
}
}
}
/***********************************************/
/* METHOD: StopLog() */
/* PURPOSE: Stop capturing log information */
/* INPUT: none */
/***********************************************/
void HSLogMan::StopLog()
{
// Stop capturing explain data for dynamic queries. Skip this if it is not
// on, to avoid clearing the diagnostics area for just-in-time logging.
if (explainOn_)
HSFuncExecQuery("CONTROL QUERY DEFAULT GENERATE_EXPLAIN RESET");
logNeeded_ = FALSE;
NABoolean logStopped = QRLogger::stopLogFile(CAT_SQL_USTAT);
}
/***********************************************/
/* METHOD: StopAndDeleteLog() */
/* PURPOSE: Stop capturing log information */
/* and then delete the log file */
/* INPUT: none */
/***********************************************/
void HSLogMan::StopAndDeleteLog()
{
StopLog();
unlink(logFile_->data());
logFile_->clear();
}
/***********************************************/
/* METHOD: ClearLog() */
/* PURPOSE: Erase the contents of the logfile */
/* INPUT: none */
/* NOTES: It is possible for the USTAT_LOG */
/* attribute to be modified many */
/* times but will not be official */
/* until either StartLog() or */
/* ClearLog() methods are called. */
/***********************************************/
void HSLogMan::ClearLog()
{
if (logFile_->length() > 0)
{
struct stat sb;
if(0==stat(logFile_->data(), &sb))//if log exists, empty it.
ofstream fileout(logFile_->data(), ios::out);
currentTimingEvent_ = -1;
startTime_[0] = 0; /* reset timer */
}
}
/***********************************************/
/* METHOD: StartTimer() */
/* PURPOSE: Capture initial timestamp for */
/* determining how long a specific */
/* operation takes. */
/* INPUT: Header/title information for a */
/* specific operation. */
/***********************************************/
void HSLogMan::StartTimer(const char *title)
{
if (logNeeded_)
{
char timerMsg[2000];
currentTimingEvent_++;
strcpy(timerMsg, ":");
for (Lng32 i=1; i<=currentTimingEvent_; i++) // no spacer for unnested event
strcat(timerMsg, "| ");
strcat(timerMsg, "BEGIN ");
if (currentTimingEvent_ >= MAX_TIMING_EVENTS) // 0-based
{
snprintf(timerMsg + strlen(timerMsg), sizeof(timerMsg) - strlen(timerMsg),
"Logging error: timing events nested too deeply at event '%s'.",
title);
Log(timerMsg);
}
else
{
strcpy(title_[currentTimingEvent_], title);
strcat(timerMsg, title);
Log(timerMsg);
startTime_[currentTimingEvent_] = NA_JulianTimestamp();
}
}
}
/***********************************************/
/* METHOD: StopTimer() */
/* PURPOSE: Capture final timestamp to */
/* determine how long a specific */
/* operation takes. Result is written */
/* to the logfile. */
/* INPUT: none */
/***********************************************/
void HSLogMan::StopTimer()
{
if (!logNeeded_)
return;
// Capture elapsed time.
Int64 timeDiff = 0;
if (currentTimingEvent_ < MAX_TIMING_EVENTS)
timeDiff = NA_JulianTimestamp() - startTime_[currentTimingEvent_];
// Prefix of timer termination string to reflect nesting level.
strcpy(msg, ":");
for (Lng32 i=1; i<=currentTimingEvent_; i++) // no spacer for unnested event
strcat(msg, "| ");
strcat(msg, "END ");
if (currentTimingEvent_ >= MAX_TIMING_EVENTS) // 0-based
{
strcat(msg, "Untimed event.");
Log(msg);
currentTimingEvent_--;
}
else if (currentTimingEvent_ >= 0 &&
startTime_[currentTimingEvent_] != 0)
{
startTime_[currentTimingEvent_] = 0;
ULng32 ms = (ULng32) (((timeDiff % 1000000) + 500) / 1000);
ULng32 sec = (ULng32) (timeDiff / 1000000);
ULng32 min = sec/60;
sec = sec % 60;
ULng32 hour = min/60;
min = min % 60;
snprintf(msg + strlen(msg), sizeof(msg) - strlen(msg),
"%s elapsed time (%02u:%02u:%02u.%03u)\n",
title_[currentTimingEvent_], hour, min, sec, ms);
Log(msg);
currentTimingEvent_--;
}
else
Log("Logging error: StopTimer() called without matching StartTimer().");
}
/***********************************************/
/* METHOD: LogTimeDiff() */
/* PURPOSE: Log time difference */
/* between calls. */
/* Result is written to the logfile. */
/* INPUT: Log message and reset flag */
/***********************************************/
void HSLogMan::LogTimeDiff(const char *text, NABoolean reset)
{
if (!logNeeded_) return;
float elapsed = 0.0;
timeval curTime;
if (gettimeofday(&curTime, 0) != 0)
Log("ERROR: gettimeofday failed.\n");
else
// calcualte time difference only when this is not a reset and prevtime has been set
if (!reset && (prevTime_.tv_sec != 0 || prevTime_.tv_usec != 0))
elapsed = (float)(curTime.tv_sec - prevTime_.tv_sec) +
(float)(curTime.tv_usec - prevTime_.tv_usec)/1000000;
snprintf(msg, sizeof(msg), "%s Time Difference %.6f\n", text, elapsed);
Log(msg);
prevTime_ = curTime;
}
/************************************************/
/* METHOD: LogTimestamp() */
/* PURPOSE: Log current timestamp in local time */
/* prefixed by the given text message. */
/* Result is written to the logfile. */
/* INPUT: Message to prepend to timestamp. */
/************************************************/
void HSLogMan::LogTimestamp(const char *text)
{
if (!logNeeded_) return;
Int64 gmtTS = NA_JulianTimestamp();
short ts[8] = {0,0,0,0,0,0,0,0};
Int64 localTS = NA_ConvertTimestamp(gmtTS);
NA_InterpretTimestamp(localTS, ts);
snprintf(msg, sizeof(msg),
"%s: %04d-%02d-%02d %02d:%02d:%02d.%03d%03d",
text, ts[0], ts[1], ts[2], ts[3], ts[4], ts[5], ts[6], ts[7]);
Log(msg);
}