diff options
Diffstat (limited to 'src/util.cpp')
| -rw-r--r-- | src/util.cpp | 99 |
1 files changed, 90 insertions, 9 deletions
diff --git a/src/util.cpp b/src/util.cpp index 0cd0133..b5b145f 100644 --- a/src/util.cpp +++ b/src/util.cpp | |||
| @@ -47,6 +47,61 @@ struct LogDedupState { | |||
| 47 | static std::map<std::string, LogDedupState> g_logDedup; | 47 | static std::map<std::string, LogDedupState> g_logDedup; |
| 48 | static CCriticalSection cs_logDedup; | 48 | static CCriticalSection cs_logDedup; |
| 49 | 49 | ||
| 50 | // A pending "(N similar)" summary, lifted out of the table while the lock is | ||
| 51 | // held and written after it is released. | ||
| 52 | struct LogDedupFlush { | ||
| 53 | std::string category; | ||
| 54 | int suppressed; | ||
| 55 | int64 elapsed; // how long the window that counted them ran | ||
| 56 | std::string lastMsg; | ||
| 57 | }; | ||
| 58 | |||
| 59 | static int64 g_logDedupLastSweep = 0; | ||
| 60 | |||
| 61 | static void EmitDedupSummary(const LogDedupFlush& f) | ||
| 62 | { | ||
| 63 | OutputDebugStringF("[%s] (%d similar in %llds, last: %s)\n", | ||
| 64 | f.category.c_str(), f.suppressed, | ||
| 65 | (long long)f.elapsed, f.lastMsg.c_str()); | ||
| 66 | } | ||
| 67 | |||
| 68 | // A window is otherwise only closed out when its own call site fires again, so | ||
| 69 | // a burst that simply stops -- 100 ledger entries voided in one pass and then | ||
| 70 | // silence -- leaves its count sitting in the table and the summary is never | ||
| 71 | // written at all. That is how 99 of 100 voids went unreported, with no "(N | ||
| 72 | // similar)" line to say anything was missing (issue #3). Sweep every expired | ||
| 73 | // window instead, so a finished burst is summarised as soon as anything at all | ||
| 74 | // is logged. | ||
| 75 | // | ||
| 76 | // Expired entries are erased rather than reopened: the window is over, so there | ||
| 77 | // is no state left worth keeping, and the next call from that site then takes | ||
| 78 | // the "first time" path and prints directly -- exactly what the rollover branch | ||
| 79 | // below would have done. It also stops the table growing without bound. | ||
| 80 | // | ||
| 81 | // Caller holds cs_logDedup; the summaries are written after it is released. | ||
| 82 | static void SweepExpiredWindowsLocked(int64 now, std::vector<LogDedupFlush>& out) | ||
| 83 | { | ||
| 84 | for (std::map<std::string, LogDedupState>::iterator it = g_logDedup.begin(); | ||
| 85 | it != g_logDedup.end(); ) | ||
| 86 | { | ||
| 87 | if (now - it->second.windowStart < DEDUP_WINDOW_SECS) | ||
| 88 | { | ||
| 89 | ++it; | ||
| 90 | continue; | ||
| 91 | } | ||
| 92 | if (it->second.suppressed > 0) | ||
| 93 | { | ||
| 94 | LogDedupFlush f; | ||
| 95 | f.category = it->first.substr(0, it->first.find('|')); | ||
| 96 | f.suppressed = it->second.suppressed; | ||
| 97 | f.elapsed = now - it->second.windowStart; | ||
| 98 | f.lastMsg = it->second.lastMsg; | ||
| 99 | out.push_back(f); | ||
| 100 | } | ||
| 101 | g_logDedup.erase(it++); | ||
| 102 | } | ||
| 103 | } | ||
| 104 | |||
| 50 | void LogPrintDedup(const char* category, const char* file, int line, const std::string& msgIn) | 105 | void LogPrintDedup(const char* category, const char* file, int line, const std::string& msgIn) |
| 51 | { | 106 | { |
| 52 | // Call sites follow the existing printf() convention of ending their | 107 | // Call sites follow the existing printf() convention of ending their |
| @@ -61,11 +116,19 @@ void LogPrintDedup(const char* category, const char* file, int line, const std:: | |||
| 61 | 116 | ||
| 62 | int64 now = GetTime(); | 117 | int64 now = GetTime(); |
| 63 | bool fEmitNow = false; | 118 | bool fEmitNow = false; |
| 64 | int suppressedToFlush = 0; | 119 | std::vector<LogDedupFlush> flushes; |
| 65 | std::string flushedMsg; | ||
| 66 | 120 | ||
| 67 | CRITICAL_BLOCK(cs_logDedup) | 121 | CRITICAL_BLOCK(cs_logDedup) |
| 68 | { | 122 | { |
| 123 | // At most once a second. The table holds one entry per call site that | ||
| 124 | // has fired in the last few seconds, so the walk is short, but it must | ||
| 125 | // not run once per line of a burst. | ||
| 126 | if (now != g_logDedupLastSweep) | ||
| 127 | { | ||
| 128 | g_logDedupLastSweep = now; | ||
| 129 | SweepExpiredWindowsLocked(now, flushes); | ||
| 130 | } | ||
| 131 | |||
| 69 | std::map<std::string, LogDedupState>::iterator it = g_logDedup.find(key); | 132 | std::map<std::string, LogDedupState>::iterator it = g_logDedup.find(key); |
| 70 | if (it == g_logDedup.end()) | 133 | if (it == g_logDedup.end()) |
| 71 | { | 134 | { |
| @@ -76,10 +139,18 @@ void LogPrintDedup(const char* category, const char* file, int line, const std:: | |||
| 76 | } | 139 | } |
| 77 | else if (now - it->second.windowStart >= DEDUP_WINDOW_SECS) | 140 | else if (now - it->second.windowStart >= DEDUP_WINDOW_SECS) |
| 78 | { | 141 | { |
| 79 | // Window rolled over: flush what was suppressed during it (if | 142 | // Window rolled over within the same second as the last sweep: |
| 80 | // anything), then start a new window with this call printed. | 143 | // flush what was suppressed during it (if anything), then start a |
| 81 | suppressedToFlush = it->second.suppressed; | 144 | // new window with this call printed. |
| 82 | flushedMsg = it->second.lastMsg; | 145 | if (it->second.suppressed > 0) |
| 146 | { | ||
| 147 | LogDedupFlush f; | ||
| 148 | f.category = category; | ||
| 149 | f.suppressed = it->second.suppressed; | ||
| 150 | f.elapsed = now - it->second.windowStart; | ||
| 151 | f.lastMsg = it->second.lastMsg; | ||
| 152 | flushes.push_back(f); | ||
| 153 | } | ||
| 83 | it->second.windowStart = now; | 154 | it->second.windowStart = now; |
| 84 | it->second.suppressed = 0; | 155 | it->second.suppressed = 0; |
| 85 | it->second.lastMsg = msg; | 156 | it->second.lastMsg = msg; |
| @@ -94,13 +165,23 @@ void LogPrintDedup(const char* category, const char* file, int line, const std:: | |||
| 94 | } | 165 | } |
| 95 | } | 166 | } |
| 96 | 167 | ||
| 97 | if (suppressedToFlush > 0) | 168 | for (size_t i = 0; i < flushes.size(); i++) |
| 98 | OutputDebugStringF("[%s] (%d similar in %llds, last: %s)\n", | 169 | EmitDedupSummary(flushes[i]); |
| 99 | category, suppressedToFlush, (long long)DEDUP_WINDOW_SECS, flushedMsg.c_str()); | ||
| 100 | if (fEmitNow) | 170 | if (fEmitNow) |
| 101 | OutputDebugStringF("[%s] %s\n", category, msg.c_str()); | 171 | OutputDebugStringF("[%s] %s\n", category, msg.c_str()); |
| 102 | } | 172 | } |
| 103 | 173 | ||
| 174 | // LogPrintAlways() -- see the doc comment in util.h. No window, no counting, | ||
| 175 | // no table entry: every call writes its own line. | ||
| 176 | void LogPrintNoCollapse(const char* category, const std::string& msgIn) | ||
| 177 | { | ||
| 178 | std::string msg = msgIn; | ||
| 179 | while (!msg.empty() && (msg.back() == '\n' || msg.back() == '\r')) | ||
| 180 | msg.pop_back(); | ||
| 181 | |||
| 182 | OutputDebugStringF("[%s] %s\n", category, msg.c_str()); | ||
| 183 | } | ||
| 184 | |||
| 104 | 185 | ||
| 105 | 186 | ||
| 106 | 187 | ||