// // Debug.h // libraries/shared/src // // Created by Zach Pomerantz on 4/12/16. // Copyright 2016 High Fidelity, Inc. // // Debug-specific classes. // To use without drastically increasing compilation time, #include in your cpp files only. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #ifndef hifi_Debug_h #define hifi_Debug_h #ifdef DEBUG #include #include // Return a string suffixed with a monotonically increasing ID, as string(ID). // Useful for Tracker instances, when many instances share the same "name". // // K must have an implemented streaming operator for QDebug operator<<. (Done for std::string in this header.) static std::string getUniqString(const std::string& s) { static std::atomic counter{ 0 }; return s + '(' + std::to_string(counter.fetch_add(1)) + ')'; } // Implement the streaming operator for std::string for ease-of-use. static QDebug operator<<(QDebug debug, const std::string& s) { QDebugStateSaver saver(debug); debug.nospace() << s.c_str(); return debug; } // A counter. Prints out counts for each key on destruction. template class Counter { using Map = std::map; public: enum LogLevel { // Only print counts for each count (e.g. "5 34 entries", meaning 34 keys have a count of 5). SUMMARY = 0, // Print SUMMARY and every key and its count. DETAILED, }; Counter(const std::string& name, LogLevel logLevel = LogLevel::DETAILED) : _logLevel{ logLevel } { _name = name; } ~Counter() { log(); } // Increase the count for key (by inc). void add(const K& key, size_t inc = 1); // Decrease the count for key (by dec). void sub(const K& key, size_t dec = 1); // Log current counts (called on destruction). void log(); private: std::string _name; std::vector _legend; LogLevel _logLevel; std::mutex _mutex; Map _map; }; // A tracker. Tracks keys state (as a size_t, mapped to std::string). // Prints out counts for each state on destruction. Optionally prints out counts for duplicates and each key's state. // // K must have an implemented streaming operator for QDebug operator<<. (Done for std::string in this header.) // // Useful cases may be tracking pointers' ctor/dtor (K = size_t or K = uintptr_t), or tracking resource names (K = std::string). template class Tracker { using Map = std::map; using DuplicateValueMap = std::map; using DuplicateMap = std::map; public: enum LogLevel { // Only print counts for each state. SUMMARY = 0, // Print SUMMARY and a count of duplicate states (when a key is set to its current state). DUPLICATES, // Print DUPLICATES and the current state for each key not in its final state (the last state of the legend). DETAILED, }; // Create a new tracker. // legend: defines a mapping from state (size_t) to that state's name. The final state is assumed to be the last state in the legend. // allowReuse: if true, keys in the last state may be set to a different state. Tracker(const std::string& name, const std::vector& legend, LogLevel logLevel = SUMMARY, bool allowReuse = true) : _logLevel{ logLevel }, _allowReuse{ allowReuse }, _max{ legend.size() - 1 } { _name = name; _legend = legend; } ~Tracker() { log(); } // Set key to state. // Fails (triggers an assertion) if (state < current state) (i.e. states are ordered), // unless allowReuse is true and the current state is the final state. void set(const K& key, const size_t& state); // Log current states (called on destruction). void log(); private: std::string _name; std::vector _legend; bool _allowReuse; size_t _max; LogLevel _logLevel; std::mutex _mutex; Map _map; DuplicateMap _duplicates; }; template void Counter::add(const K& k, size_t inc) { std::lock_guard lock(_mutex); auto& it = _map.find(k); if (it == _map.end()) { // No entry for k; add it _map.insert(std::pair(k, inc)); } else { // Entry for k; update it it->second += inc; } } template void Counter::sub(const K& k, size_t dec) { add(k, -dec); } template void Counter::log() { // Avoid logging nothing if (!_map.size()) return; std::map results; for (const auto& entry : _map) { if (!results.insert(std::pair(entry.second, 1)).second) { results.find(entry.second)->second++; } } qCDebug(shared) << "Counts for" << _name; for (const auto& entry : results) { qCDebug(shared) << entry.first << '\t' << entry.second << "entries"; } if (_logLevel == LogLevel::SUMMARY) return; qCDebug(shared) << "Entries"; for (const auto& entry : _map) { qCDebug(shared) << entry.first << '\t' << entry.second; } if (_logLevel == LogLevel::DETAILED) return; } template void Tracker::set(const K& k, const size_t& v) { std::lock_guard lock(_mutex); auto& it = _map.find(k); if (it == _map.end()) { // No entry for k; add it _map.insert(std::pair(k, v)); } else if (v > it->second) { // Ordered entry for k; update it it->second = v; } else if (v == it->second) { // Duplicate entry for k; log it auto& dit = _duplicates.find(k); if (dit == _duplicates.end()) { // No duplicate value map for k; add it DuplicateValueMap duplicateValueMap{ std::pair(v, 1) }; _duplicates.insert(std::pair(k, duplicateValueMap)); } else { // Duplicate value map for k; update it auto& dvit = dit->second.find(v); if (dvit == dit->second.end()) { // No duplicate entry for (k, v); add it dit->second.insert(std::pair(v, 1)); } else { // Duplicate entry for (k, v); update it dvit->second++; } } } else { // if (v < it.second) if (_allowReuse && it->second == _max) { // Reusing an entry; update it it->second = v; } else { // Unordered entry for k; dump log and fail qCDebug(shared) << "Badly ordered entry detected:" << k << _legend.at(it->second).c_str() << "->" << _legend.at(v).c_str(); log(); assert(false); } } } template void Tracker::log() { // Avoid logging nothing if (!_map.size()) return; std::vector> results(_max + 1); for (const auto& entry : _map) { results.at(entry.second).push_back(entry.first); } qCDebug(shared) << "Summary of" << _name; for (auto i = 0; i < results.size(); ++i) { qCDebug(shared) << _legend.at(i) << '\t' << results[i].size() << "entries"; } if (_logLevel == LogLevel::SUMMARY) return; size_t size = 0; for (const auto& entry : _duplicates) { size += entry.second.size(); } qCDebug(shared) << "Duplicates" << size << "entries"; // TODO: Add more detail to duplicate logging if (_logLevel <= LogLevel::DUPLICATES) return; qCDebug(shared) << "Entries"; // Don't log the terminal case for (auto i = 0; i < _max; ++i) { qCDebug(shared) << "----" << _legend.at(i) << "----"; for (const auto& entry : results[i]) { qCDebug(shared) << "\t" << entry; } } if (_logLevel <= LogLevel::DETAILED) return; } #endif // DEBUG #endif // hifi_Debug_h