// (C) Copyright 2014 by Autodesk, Inc. // // The information contained herein is confidential, proprietary // to Autodesk, Inc., and considered a trade secret as defined // in section 499C of the penal code of the State of California. // Use of this information by anyone other than authorized // employees of Autodesk, Inc. is granted only under a written // non-disclosure agreement, expressly prescribing the scope // and manner of such use. #include "DebUtils.hh" #include "Base/Utils/ThrowError.hh" #ifdef DEB_ON #include #include #include #include #include #include namespace { // LOCAL_PROC bool is_html_filename(const char* const str) { if (str == nullptr) return false; const char* dot = strrchr(str, '.'); if (dot == nullptr) return false; ++dot; return (!strncmp(dot, "htm", 3)) || (!strncmp(dot, "HTM", 3)) ; } } namespace Debug { class FunctionCallSequence { std::string func_name_; std::vector debs_; // These may not have sequential counts when multithreaded. public: FunctionCallSequence(const char* _func_name, Enter* _deb) : func_name_(_func_name) { debs_.push_back(_deb); } ~FunctionCallSequence() {} bool add(const char* _func_name, Enter* _deb) { if (func_name_ == _func_name) { debs_.push_back(_deb); return true; } return false; } bool pop() { if (debs_.size() > 1) { debs_.pop_back(); return true; } debs_.clear(); return false; } int number_calls() const { if (!this) return 0; return (int)debs_.size(); } int count(int i = 0) const { int num = number_calls(); if (i < num) return debs_[num - 1 - i]->count_; return -1; } const char* name() const { return func_name_.c_str(); } // Replace interior of < > in function name with . for brevity void compact_name(std::string& str) const { int cnt = 0; const char* ptr = func_name_.c_str(); while (ptr && (*ptr != '\0')) { char c = *ptr; if (c == '>') --cnt; if (cnt == 0) str.append(1, c); if (c == '<') { if (cnt == 0) str.append("."); ++cnt; } ++ptr; } } // Get single call stack element string void get(std::string& _str, const bool _strip_angled, bool _with_counts) const { if (_strip_angled) compact_name(_str); else _str.append(name()); _str.append("["); if (_with_counts) { int num = number_calls(); int prev = -2; int seq_cnt = 0; for (int i = 0; i < num; ++i) { int cnt = debs_[i]->count_; if (cnt != prev + 1) { char buffer[64]; if (seq_cnt > 0) { _str.append("-"); sprintf_s(buffer, sizeof(buffer), "%i", prev); _str.append(buffer); } if (i > 0) _str.append(","); sprintf_s(buffer, sizeof(buffer), "%i", cnt); _str.append(buffer); seq_cnt = 0; } else ++seq_cnt; prev = cnt; } // endfor i } // endif _with_counts else _str.append("*"); _str.append("]"); } // endfunc get void get_indent(std::string& _str, File* _dfile, bool _is_html); }; // endclass FunctionCallSequence class CallStack { std::vector calls_; int depth_; public: CallStack() : depth_(0) {} ~CallStack() {} void add(const char* _func_name, Enter* _deb) { if (calls_.empty() || !calls_.back().add(_func_name, _deb)) calls_.push_back(FunctionCallSequence(_func_name, _deb)); ++depth_; } void pop() { if (!calls_.back().pop()) calls_.pop_back(); --depth_; } const FunctionCallSequence* call(int _up = 0) { int num = (int)calls_.size(); if (_up < num) return &calls_[num - 1 - _up]; return nullptr; } // Read a particular call stack element bool read(int _up, const char*& _funcname, int& _count) { const FunctionCallSequence* fcs = call(_up); if (fcs != nullptr) { _funcname = fcs->name(); _count = fcs->count(0); // Return most recent deb_enter_count return true; } return false; } // Get a full call stack sting. // returns number of call stack function elements int get(std::string& _str, bool _with_counts = true) const { int num = (int)calls_.size(); for (int i = 0; i < num; ++i) { if (i > 0) _str.append("->"); calls_[i].get(_str, true, _with_counts); } return num; } int depth() const { return depth_; } bool get_indent(std::string& _str, File* _dfile, const bool is_html); }; // endclass CallStack class File { private: class module_stats { public: int lev_; // Level set for this module int col_; // Colour (24 bit RGB, MSB=Red) for this module's DEB_out module_stats(int _lev = 0) : lev_(_lev), col_(0x808080) {} ~module_stats() {} }; std::map module_map_; typedef std::map::iterator module_map_itr; typedef std::map::const_iterator const_module_map_itr; Stream::StreamType type_; int lev_; int num_flush_; int priority_; // Last permission granted int indent_size_; bool at_line_start_; std::string current_; std::string output_; std::string file_name_; std::fstream file_stream_; std::string double_format_; //std::string indent_string_; Stream* deb_stream_; CallStack call_stack_; public: CallStack& call_stack() { return call_stack_; } bool is_kept_open() const { return 0 != (type_ & Stream::StreamType::KEEP_OPEN); } bool is_html() const { return 0 != (type_ & Stream::StreamType::HTML); } bool is_retained() const { return 0 != (type_ & Stream::StreamType::RETAIN); } bool is_appended() const { return 0 != (type_ & Stream::StreamType::APPEND); } // Only applies to HTML DEB_out bool is_white_on_black() const { return true; } int indent_size() { return indent_size_; } bool file_is_open() const { return file_stream_.is_open(); } int priority() const { return priority_; } bool fork_to_cout() { return false; } bool fork_to_cerr() { return true; } const char* file_name() const { if (this && (!file_name_.empty())) return file_name_.c_str(); return nullptr; } void clear() { current_.clear(); output_.clear(); file_name_.clear(); } char prev_char() const { if (!current_.empty()) return current_.back(); if (!output_.empty()) return output_.back(); return '\0'; } void indent(bool _full_text) { std::string str; if (call_stack().get_indent(str, this, _full_text && is_html())) current_.append(str); } void line_break(bool _with_indent = false) { if (is_html()) current_.append("
"); // Don't bother with matching
current_.append("\n", 1); if (_with_indent) indent(false); else at_line_start_ = true; } void set_level(int _lev) { lev_ = _lev; } void print_direct(const std::string& _s) { current_.append(_s); } void print(const char _c) { if (_c == '\n') { line_break(); return; } if (at_line_start_) { indent(true); at_line_start_ = false; } if (is_html()) { // translate the esoteric characters used in IGM DEB_out if (_c == 'Â') // -62 return; if (_c == '§') // 167 = -89 { current_.append("§"); return; } if (_c == '°') // -80 { current_.append("°"); return; } } current_.append(&_c, 1); } void print_to_ostream(const char* const _s, std::ostream& os) { os << _s; } void print(const char* const _s, bool _fork = true) { if (_s != nullptr) { for (int i = 0; ; ++i) { const char c = _s[i]; if (c == '\0') break; print(c); } if (_fork) { if (fork_to_cout()) print_to_ostream(_s, std::cout); if (fork_to_cerr()) print_to_ostream(_s, std::cerr); } } } void print(int _i) { char buffer[64]; sprintf_s(buffer, sizeof(buffer), "%i", _i); print(buffer); } const char* double_format() const { if (double_format_.empty()) return "%.17g"; return double_format_.c_str(); } void set_double_format(const char* const str) { if (str == nullptr) double_format_.clear(); else double_format_ = str; } void print(double _d) { char buffer[64]; sprintf_s(buffer, sizeof(buffer), double_format(), _d); print(buffer); } void print(const Command& _co) { switch (_co.com()) { case Command::END : if (is_html()) print_direct(""); break; case Command::END_ERR : // Powerdown DEB_error font // if (is_html()) print_direct(""); // fall through case Command::END_LF : if (is_html()) print_direct(""); line_break(); // line_break() does not fork to cout or cerr // so do so explicitly. if (fork_to_cout()) std::cout << "\n"; if (fork_to_cerr()) std::cerr << "\n"; break; } } Stream& stream() { return *deb_stream_; } // Append current asctime to given string bool add_time(std::string& str) { time_t rawtime; time(&rawtime); struct tm timeinfo; errno_t err = localtime_s(&timeinfo, &rawtime); if (err == 0) { char buffer[256]; err = asctime_s(buffer, sizeof(buffer), &timeinfo); if (err == 0) { str.append(buffer); return true; } } return false; } #if 1 bool hover(std::string& _str, const std::string& _hover, const bool _open) { if (is_html()) { char buffer[1024]; if (_open) sprintf_s(buffer, sizeof(buffer), "", _hover.c_str()); else sprintf_s(buffer, sizeof(buffer), ""); _str.append(buffer); return true; } return false; } #endif bool anchor(std::string& _str, const int _id, const char* _tag, const bool _open) { if (is_html()) { char buffer[1024]; if (_open) sprintf_s(buffer, sizeof(buffer), "", _id, _tag); else sprintf_s(buffer, sizeof(buffer), ""); _str.append(buffer); return true; } return false; } bool link_to(std::string& _str, const int _id, const char* _tag, const std::string& _hover, const bool _open) { if (is_html()) { char buffer[2048]; if (_open) { // HTML title hover text is cropped to 64 char in Firefox but displays // OK in Chrome. We could use javascript to avoid this limit but HTML // is simpler. if (_hover.empty()) sprintf_s(buffer, sizeof(buffer), "", _id, _tag); else sprintf_s(buffer, sizeof(buffer), "", _id, _tag, _hover.c_str()); } else sprintf_s(buffer, sizeof(buffer), ""); _str.append(buffer); return true; } return false; } void header(std::string& str) { if (is_html()) { str.append(""); str.append("\n"); str.append("\nReForm DEB_out"); str.append(""); // javascript lib loads go here // stylesheet loads go here // within HEAD javascript goes here str.append("\n"); if (is_white_on_black()) { str.append("\n"); //str.append( "\n"); } else { str.append("\n"); //str.append( "\n"); } str.append("\n"); } // endif is_html bool date_header = true; if (date_header) { if (!file_name_.empty()) { str.append(file_name_); str.append(" opened "); } add_time(str); str.append("[ Build: " __TIME__ " " __DATE__ "] "); #ifdef DEB_SHOW_ON // Add link in deb_out.htm to deb_show.htm if (is_html()) str.append(" :: ReForm_deb_show.htm
\n"); #endif if (is_html()) str.append("
"); str.append("\n"); } } void footer() { bool date_footer = true; if (date_footer) { std::string str("\n"); if (!file_name_.empty()) str.append(file_name_); str.append(" Closed: "); add_time(str); stream() << str << "\n"; } if (is_html()) stream().print("\n", false); } bool is_first_flush() { return num_flush_ == 0 ; } int flush() { int res = 0; if (!current_.empty()) { const char* fname = file_name(); if ((fname != nullptr) || file_is_open()) { if (!file_is_open()) { file_stream_.open(fname, std::fstream::out | ((is_appended() && !is_first_flush()) ? std::fstream::app : std::fstream::trunc)); } if (file_stream_.is_open()) { std::string hdr; if (!is_appended()) { // Reoutput entire file header(hdr); output_.append(hdr); file_stream_ << output_; } else { if (is_first_flush()) { header(hdr); if (is_retained()) output_.append(hdr); file_stream_ << hdr; } ++num_flush_; } file_stream_ << current_; if (is_retained()) output_.append(current_); current_.clear(); if (!is_kept_open()) file_stream_.close(); } // endif fs.is_open else res = -1; } } // endif current empty return res; } // endfunc flush // Use with extreme caution. const char * string_out() const { return current_.c_str(); } void close() { footer(); flush(); } void set_file_name(const char* _name) { file_name_ = _name ? _name : ""; if (is_html_filename(_name)) type_ = (Stream::StreamType)(type_ | Stream::StreamType::HTML); } void set_module_level(const char* const _module, const int _lev) { if (this) { std::pair ins = module_map_.insert( std::pair(_module, module_stats(_lev))); if (!ins.second) ins.first->second.lev_ = _lev; } } int module_level(const char* const _module) const { if (!this) return -1; const_module_map_itr it = module_map_.find(std::string(_module)); if (it == module_map_.end()) return lev_; return it->second.lev_; } void set_module_color(const char* const _module, const int _col) { if (this) { std::pair ins = module_map_.insert( std::pair(_module, module_stats(lev_))); ins.first->second.col_ = _col; } } int get_module_color(const char* const _module) const { const_module_map_itr it = module_map_.find(std::string(_module)); if (it == module_map_.end()) { if (is_white_on_black()) return 0xFFFFFF; else return 0x000000; } return it->second.col_; } int permission(const int _lev, const int _warn, const char* const _module) { _warn; int lev = module_level(_module); lev -= _lev; if (lev < 0) lev = 0; if (lev > 0) priority_ = lev; return lev; } bool is_at_line_start() { return at_line_start_; } File( Stream* _deb_stream, Stream::StreamType _type = (Stream::StreamType)(Stream::APPEND | Stream::RETAIN), const char* _file_name = nullptr) : type_(_type), lev_(5), deb_stream_(_deb_stream), num_flush_(0) { set_file_name(_file_name); indent_size_ = 3; at_line_start_ = false; // Don't want to indent header // indent_string_ = "."; set_module_color("PARA", 0xFF8000); set_module_color("SOLV", 0x00FF00); set_module_color("NSLV", 0xFFFF00); set_module_color("FELD", 0x0080FF); set_module_color("CURV", 0x00FFFF); set_module_color("QMGN", 0xFF00FF); // Don't set a TEST module colour here because Test is not part of Reform #if 0 // Such custom setting calls really belong in Test set_module_level("PARA", 1); set_module_level("CFLD", 0); set_module_level("NSLV", 0); set_module_level("QMGN", 0); set_module_level("CURV", 5); set_module_level("SOLV", 0); set_module_level("FELD", 0); #endif } ~File() { // The closing of the primary DEB_out file is a good place to flush and close // any subsidary DEB files #ifdef DEB_SHOW_ON if (type_ & Stream::PRIMARY) { DebShow::DebShowStream os; os << file_name_ << " closing"; DebShow::DEB_show_close(os.string_out()); } #endif } }; // endclass File // ===================================== // Enter member funcs // ===================================== Enter::Enter(const char* const _funcname, const int _count, const char* const _module) { // TODO: this might have to be atomic static int id_cnt = 0; module_ = _module; deb_outs_ = 0; deb_lines_ = 0; id_ = ++id_cnt; count_ = _count; stream(0, false).dfile()->call_stack().add(_funcname, this); } Enter::~Enter() { File* impl = stream(0, false).dfile(); impl->call_stack().pop(); std::string str; if (((deb_outs_ > 0) || (deb_lines_ > 0)) && impl->anchor(str, id_, "exit", true)) { impl->anchor(str, id_, "exit", false); impl->print_direct(str); } } Stream& Enter::stream(const int _warn, const bool _print) { Stream& ds = Stream::get_global(_warn); File* impl = ds.dfile(); if (_print) { if (impl->is_html()) { bool is_deb_error = (_warn == 2); // DEB_error font powerup goes here. BLINK is deprecated sadly. // if (is_deb_error) impl->print_direct(""); int col = impl->get_module_color(module_); if (is_deb_error) col = 0xFF0000; // RED char buffer[256]; sprintf_s(buffer, sizeof(buffer), "", col, impl->priority() + 1); impl->print_direct(buffer); } if (deb_outs_ < 1) { // First DEB_out in this function so output callstack, and flush. impl->indent(true); std::string str; bool is_html = impl->is_html(); if (is_html) { str.append(""); impl->anchor(str, id_, "enter", true); } else { // .txt call stack lead in str.append("****>"); } impl->call_stack().get(str); if (is_html) str.append(""); impl->print_direct(str.c_str()); // Don't fork callstack to cerr etc. impl->line_break(); ds.dfile()->flush(); } ++deb_outs_; } return ds; } int Enter::permission(const int _lev, const int _warn) { int res = Stream::get_global().dfile()->permission(_lev, _warn, module_); return res; } void FunctionCallSequence::get_indent(std::string& _str, File* _dfile, bool _is_html) { int num = number_calls(); for (int i = 0; i < num; ++i) { Enter* deb = debs_[i]; if (_is_html) { /* HTML indent element is with span title the name and count of the function and with < linking to the entry anchor (if present) and > linking to the exit anchor (will be present). L is the first letter of the module name */ char hovert[1024]; sprintf_s(hovert, sizeof(hovert), "%s[%i]", func_name_.c_str(), deb->count_); int col = _dfile->get_module_color(deb->module_); char buffer[1024]; sprintf_s(buffer, sizeof(buffer), ".", col); _dfile->hover(_str, std::string(hovert), true); _str.append(buffer); std::string cstk; //impl->call_stack().get(cstk); if ((deb->deb_outs_ > 0) && _dfile->link_to(_str, deb->id_, "enter", cstk, true)) { _str.append("<"); _dfile->link_to(_str, deb->id_, "enter", cstk, false); } else _str.append("<"); _str.append(deb->module_, 1); if (_dfile->link_to(_str, deb->id_, "exit", cstk, true)) { _str.append(">"); _dfile->link_to(_str, deb->id_, "exit", cstk, false); ++deb->deb_lines_; } _dfile->hover(_str, std::string(hovert), false); _str.append(""); } // endif html else _str.append(" "); } } bool CallStack::get_indent(std::string& _str, File* _dfile, const bool is_html) { if (_dfile->indent_size() == 0) return false; if (is_html) { char buffer[64]; sprintf_s(buffer, sizeof(buffer), "", _dfile->indent_size()); _str.append(buffer); } int num = (int)calls_.size(); int i0 = 0; if (!is_html) ++i0; // Don't waste whitespace on first level indent if .txt for (int i = i0; i < num; ++i) calls_[i].get_indent(_str, _dfile, is_html); if (is_html) _str.append(": \n"); return true; } // ===================================== // Stream member funcs // ===================================== Stream::Stream(const char* _file_name, StreamType _type) { // NB. disable DEB_out over this new if new contains DEB_out dfile_ = new File(this, _type, _file_name); } Stream::~Stream() { if (dfile_ != nullptr) { dfile()->close(); dfile()->clear(); // NB. disable DEB_out over this delete if delete contains DEB_out delete dfile_; dfile_ = nullptr; } } const char * Stream::string_out() const { return dfile()->string_out();} Stream& Stream::print(const int _i) { dfile_->print(_i); return *this; }; Stream& Stream::print(const double _d) { dfile_->print(_d); return *this; }; Stream& Stream::print(const char* _s, bool _fork) { dfile_->print(_s, _fork); return *this; }; Stream& Stream::print(const char _c) { dfile_->print(_c); return *this; }; Stream& Stream::print(const Command& _co) { dfile_->print(_co); return *this; }; Stream& Stream::get_global(int _warn) { _warn; // TODO: Replace with a Singleton?? ThreadArray?? #ifdef DEB_SHOW_ON static Stream g_ds__( "../../../../test/logs/reform_deb_out.htm", (StreamType)(APPEND | PRIMARY) ); #else // DEB_SHOW_ON static Stream g_ds__("reform_deb_out.txt"); #endif // DEB_SHOW_ON return g_ds__; } Stream& operator<<(Stream& _ds, const int _i) { return _ds.print(_i); } Stream& operator<<(Stream& _ds, const double _d) { return _ds.print(_d); } Stream& operator<<(Stream& _ds, const char* const _s) { return _ds.print(_s); } Stream& operator<<(Stream& _ds, const char _c) { return _ds.print(_c); } Stream& operator<<(Stream& _ds, const size_t _i) { return _ds.print((int)_i); } Stream& operator<<(Stream& _ds, const unsigned int _i) { return _ds.print((int)_i); } Stream& operator<<(Stream& _ds, const float _f) { return _ds.print((double)_f); } Stream& operator<<(Stream& _ds, const std::string& _s) { return _ds.print(_s.c_str()); } Stream& operator<<(Stream& _ds, const Command& _co) { return _ds.print(_co); } Stream& operator<<(Stream& _ds, const ThrowInfo& _thrw_info) { _ds << "thrown by " << _thrw_info.fnct_ << "() in " << _thrw_info.modl_ << " file " << _thrw_info.file_ << " @ line " << _thrw_info.line_; return _ds; } // ================================================== // Controller member funcs (currently all static) // ================================================== void Controller::set_module_level(const char* _mod , const int _lev) { Stream::get_global().dfile()->set_module_level(_mod, _lev); } void Controller::set_double_format(const char* _fmt) { Stream::get_global().dfile()->set_double_format(_fmt); } int Controller::module_level(const char* _mod) { return Stream::get_global().dfile()->module_level(_mod); } const char* Controller::double_format() { return Stream::get_global().dfile()->double_format(); } }//namespace Debug #endif // DEB_ON