From 774eb1c45a4396fdd8962bd963f80cd38531823d Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 13 Apr 2016 00:28:11 -0700 Subject: [PATCH] Make shared Debug header --- libraries/shared/src/Debug.h | 249 +++++++++++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 libraries/shared/src/Debug.h diff --git a/libraries/shared/src/Debug.h b/libraries/shared/src/Debug.h new file mode 100644 index 0000000000..d5568749d4 --- /dev/null +++ b/libraries/shared/src/Debug.h @@ -0,0 +1,249 @@ +// +// 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. + void add(const K& key); + + // 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) { + 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, 1)); + } else { + // Entry for k; update it + it->second++; + } +} + +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++; + } + } + + qDebug() << "Counts for" << _name; + for (const auto& entry : results) { + qDebug() << entry.first << '\t' << entry.second << "entries"; + } + if (_logLevel == LogLevel::SUMMARY) return; + + qDebug() << "Entries"; + for (const auto& entry : _map) { + qDebug() << 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 + qDebug() << "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); + } + + qDebug() << "Summary of" << _name; + for (auto i = 0; i < results.size(); ++i) { + qDebug() << _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(); + } + qDebug() << "Duplicates" << size << "entries"; + // TODO: Add more detail to duplicate logging + if (_logLevel <= LogLevel::DUPLICATES) return; + + qDebug() << "Entries"; + // Don't log the terminal case + for (auto i = 0; i < _max; ++i) { + qDebug() << "----" << _legend.at(i) << "----"; + for (const auto& entry : results[i]) { + qDebug() << "\t" << entry; + } + } + if (_logLevel <= LogLevel::DETAILED) return; +} + +#endif // DEBUG + +#endif // hifi_Debug_h +