/*
* Copyright (C) Texas Instruments - http://www.ti.com/
*
* Licensed 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.
*/
#ifndef DEBUG_UTILS_H
#define DEBUG_UTILS_H
#include <android/log.h>
#include <utils/threads.h>
#include <utils/Vector.h>
namespace Ti {
// use 2 space characters for call stack indent
static const int kFunctionLoggerIndentSize = 2;
template <int Size = kFunctionLoggerIndentSize>
class IndentString
{
public:
IndentString(int length);
const char * string() const;
private:
int calculateOffset(int length) const;
private:
const int mOffset;
};
class Debug
{
public:
static Debug * instance();
int offsetForCurrentThread();
void log(int priority, const char * format, ...);
private:
class ThreadInfo
{
public:
ThreadInfo() :
threadId(0), callOffset(0)
{}
volatile int32_t threadId;
int callOffset;
};
class Data : public android::RefBase
{
public:
android::Vector<ThreadInfo*> threads;
};
private:
// called from FunctionLogger
void increaseOffsetForCurrentThread();
void decreaseOffsetForCurrentThread();
private:
Debug();
void grow();
ThreadInfo * registerThread(Data * data, int32_t threadId);
ThreadInfo * findCurrentThreadInfo();
void addOffsetForCurrentThread(int offset);
private:
static Debug sInstance;
mutable android::Mutex mMutex;
android::sp<Data> mData;
friend class FunctionLogger;
};
class FunctionLogger
{
public:
FunctionLogger(const char * file, int line, const char * function);
~FunctionLogger();
void setExitLine(int line);
private:
const char * const mFile;
const int mLine;
const char * const mFunction;
const void * const mThreadId;
int mExitLine;
};
#ifdef TI_UTILS_FUNCTION_LOGGER_ENABLE
# define LOG_FUNCTION_NAME Ti::FunctionLogger __function_logger_instance(__FILE__, __LINE__, __FUNCTION__);
# define LOG_FUNCTION_NAME_EXIT __function_logger_instance.setExitLine(__LINE__);
#else
# define LOG_FUNCTION_NAME int __function_logger_instance;
# define LOG_FUNCTION_NAME_EXIT (void*)__function_logger_instance;
#endif
#ifdef TI_UTILS_DEBUG_USE_TIMESTAMPS
// truncate timestamp to 1000 seconds to fit into 6 characters
# define TI_UTILS_DEBUG_TIMESTAMP_TOKEN "[%06d] "
# define TI_UTILS_DEBUG_TIMESTAMP_VARIABLE static_cast<int>(nanoseconds_to_milliseconds(systemTime()) % 1000000),
#else
# define TI_UTILS_DEBUG_TIMESTAMP_TOKEN
# define TI_UTILS_DEBUG_TIMESTAMP_VARIABLE
#endif
#define DBGUTILS_LOGV_FULL(priority, file, line, function, format, ...) \
do \
{ \
Ti::Debug * const debug = Ti::Debug::instance(); \
debug->log(priority, format, \
TI_UTILS_DEBUG_TIMESTAMP_VARIABLE \
reinterpret_cast<int>(androidGetThreadId()), \
Ti::IndentString<>(debug->offsetForCurrentThread()).string(), \
file, line, function, __VA_ARGS__); \
} while (0)
#define DBGUTILS_LOGV(...) DBGUTILS_LOGV_FULL(ANDROID_LOG_VERBOSE, __FILE__, __LINE__, __FUNCTION__, TI_UTILS_DEBUG_TIMESTAMP_TOKEN "(%x) %s %s:%d %s - " __VA_ARGS__, "")
#define DBGUTILS_LOGD(...) DBGUTILS_LOGV_FULL(ANDROID_LOG_DEBUG, __FILE__, __LINE__, __FUNCTION__, TI_UTILS_DEBUG_TIMESTAMP_TOKEN "(%x) %s %s:%d %s - " __VA_ARGS__, "")
#define DBGUTILS_LOGI(...) DBGUTILS_LOGV_FULL(ANDROID_LOG_INFO, __FILE__, __LINE__, __FUNCTION__, TI_UTILS_DEBUG_TIMESTAMP_TOKEN "(%x) %s %s:%d %s - " __VA_ARGS__, "")
#define DBGUTILS_LOGW(...) DBGUTILS_LOGV_FULL(ANDROID_LOG_WARN, __FILE__, __LINE__, __FUNCTION__, TI_UTILS_DEBUG_TIMESTAMP_TOKEN "(%x) %s %s:%d %s - " __VA_ARGS__, "")
#define DBGUTILS_LOGE(...) DBGUTILS_LOGV_FULL(ANDROID_LOG_ERROR, __FILE__, __LINE__, __FUNCTION__, TI_UTILS_DEBUG_TIMESTAMP_TOKEN "(%x) %s %s:%d %s - " __VA_ARGS__, "")
#define DBGUTILS_LOGF(...) DBGUTILS_LOGV_FULL(ANDROID_LOG_FATAL, __FILE__, __LINE__, __FUNCTION__, TI_UTILS_DEBUG_TIMESTAMP_TOKEN "(%x) %s %s:%d %s - " __VA_ARGS__, "")
#define DBGUTILS_LOGVA DBGUTILS_LOGV
#define DBGUTILS_LOGVB DBGUTILS_LOGV
#define DBGUTILS_LOGDA DBGUTILS_LOGD
#define DBGUTILS_LOGDB DBGUTILS_LOGD
#define DBGUTILS_LOGEA DBGUTILS_LOGE
#define DBGUTILS_LOGEB DBGUTILS_LOGE
// asserts
#define _DBGUTILS_PLAIN_ASSERT(condition) \
do \
{ \
if ( !(condition) ) \
{ \
__android_log_print(ANDROID_LOG_FATAL, "Ti::Debug", \
"Condition failed: " #condition); \
__android_log_print(ANDROID_LOG_FATAL, "Ti::Debug", \
"Aborting process..."); \
abort(); \
} \
} while (0)
#define _DBGUTILS_PLAIN_ASSERT_X(condition, ...) \
do \
{ \
if ( !(condition) ) \
{ \
__android_log_print(ANDROID_LOG_FATAL, "Ti::Debug", \
"Condition failed: " #condition ": " __VA_ARGS__); \
__android_log_print(ANDROID_LOG_FATAL, "Ti::Debug", \
"Aborting process..."); \
abort(); \
} \
} while (0)
#define DBGUTILS_ASSERT(condition) \
do \
{ \
if ( !(condition) ) \
{ \
DBGUTILS_LOGF("Condition failed: " #condition); \
DBGUTILS_LOGF("Aborting process..."); \
abort(); \
} \
} while (0)
#define DBGUTILS_ASSERT_X(condition, ...) \
do \
{ \
if ( !(condition) ) \
{ \
DBGUTILS_LOGF("Condition failed: " #condition ": " __VA_ARGS__); \
DBGUTILS_LOGF("Aborting process..."); \
abort(); \
} \
} while (0)
static const int kIndentStringMaxLength = 128;
template <int Size>
inline int IndentString<Size>::calculateOffset(const int length) const
{
const int offset = kIndentStringMaxLength - length*Size;
return offset < 0 ? 0 : offset;
}
template <int Size>
inline IndentString<Size>::IndentString(const int length) :
mOffset(calculateOffset(length))
{}
template <int Size>
inline const char * IndentString<Size>::string() const
{
extern const char sIndentStringBuffer[];
return sIndentStringBuffer + mOffset;
}
inline Debug * Debug::instance()
{ return &sInstance; }
inline Debug::ThreadInfo * Debug::findCurrentThreadInfo()
{
// retain reference to threads data
android::sp<Data> data = mData;
// iterate over threads to locate thread id,
// this is safe from race conditions because each thread
// is able to modify only his own ThreadInfo structure
const int32_t threadId = reinterpret_cast<int32_t>(androidGetThreadId());
const int size = int(data->threads.size());
for ( int i = 0; i < size; ++i )
{
ThreadInfo * const threadInfo = data->threads.itemAt(i);
if ( threadInfo->threadId == threadId )
return threadInfo;
}
// this thread has not been registered yet,
// try to fing empty thread info slot
while ( true )
{
ThreadInfo * const threadInfo = registerThread(data.get(), threadId);
if ( threadInfo )
return threadInfo;
// failed registering thread, because all slots are occupied
// grow the data and try again
grow();
data = mData;
}
// should never reach here
_DBGUTILS_PLAIN_ASSERT(false);
return 0;
}
inline void Debug::addOffsetForCurrentThread(const int offset)
{
if ( offset == 0 )
return;
ThreadInfo * const threadInfo = findCurrentThreadInfo();
_DBGUTILS_PLAIN_ASSERT(threadInfo);
threadInfo->callOffset += offset;
if ( threadInfo->callOffset == 0 )
{
// thread call stack has dropped to zero, unregister it
android_atomic_acquire_store(0, &threadInfo->threadId);
}
}
inline int Debug::offsetForCurrentThread()
{
#ifdef TI_UTILS_FUNCTION_LOGGER_ENABLE
ThreadInfo * const threadInfo = findCurrentThreadInfo();
_DBGUTILS_PLAIN_ASSERT(threadInfo);
return threadInfo->callOffset;
#else
return 0;
#endif
}
inline void Debug::increaseOffsetForCurrentThread()
{
#ifdef TI_UTILS_FUNCTION_LOGGER_ENABLE
addOffsetForCurrentThread(1);
#endif
}
inline void Debug::decreaseOffsetForCurrentThread()
{
#ifdef TI_UTILS_FUNCTION_LOGGER_ENABLE
addOffsetForCurrentThread(-1);
#endif
}
inline void Debug::log(const int priority, const char * const format, ...)
{
va_list args;
va_start(args, format);
__android_log_vprint(priority, LOG_TAG, format, args);
va_end(args);
}
inline FunctionLogger::FunctionLogger(const char * const file, const int line, const char * const function) :
mFile(file), mLine(line), mFunction(function), mThreadId(androidGetThreadId()), mExitLine(-1)
{
Debug * const debug = Debug::instance();
debug->increaseOffsetForCurrentThread();
android_printLog(ANDROID_LOG_DEBUG, LOG_TAG,
TI_UTILS_DEBUG_TIMESTAMP_TOKEN "(%x) %s+ %s:%d %s - ENTER",
TI_UTILS_DEBUG_TIMESTAMP_VARIABLE
(int)mThreadId, IndentString<>(debug->offsetForCurrentThread()).string(),
mFile, mLine, mFunction);
}
inline FunctionLogger::~FunctionLogger()
{
Debug * const debug = Debug::instance();
android_printLog(ANDROID_LOG_DEBUG, LOG_TAG,
TI_UTILS_DEBUG_TIMESTAMP_TOKEN "(%x) %s- %s:%d %s - EXIT",
TI_UTILS_DEBUG_TIMESTAMP_VARIABLE
(int)mThreadId, IndentString<>(debug->offsetForCurrentThread()).string(),
mFile, mExitLine == -1 ? mLine : mExitLine, mFunction);
debug->decreaseOffsetForCurrentThread();
}
inline void FunctionLogger::setExitLine(const int line)
{
if ( mExitLine != -1 )
{
Debug * const debug = Debug::instance();
android_printLog(ANDROID_LOG_DEBUG, LOG_TAG,
TI_UTILS_DEBUG_TIMESTAMP_TOKEN "(%x) %s %s:%d %s - Double function exit trace detected. Previous: %d",
TI_UTILS_DEBUG_TIMESTAMP_VARIABLE
(int)mThreadId, IndentString<>(debug->offsetForCurrentThread()).string(),
mFile, line, mFunction, mExitLine);
}
mExitLine = line;
}
} // namespace Ti
#endif //DEBUG_UTILS_H