diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/rpc.cpp | 10 | ||||
| -rw-r--r-- | src/util.cpp | 99 | ||||
| -rw-r--r-- | src/util.h | 22 |
3 files changed, 114 insertions, 17 deletions
diff --git a/src/rpc.cpp b/src/rpc.cpp index a4b53ca..2e8f4b8 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp | |||
| @@ -1732,7 +1732,9 @@ static void FlushMaturePayouts() | |||
| 1732 | } | 1732 | } |
| 1733 | int64 total = 0; | 1733 | int64 total = 0; |
| 1734 | for (auto& kv : it->amounts) total += kv.second; | 1734 | for (auto& kv : it->amounts) total += kv.second; |
| 1735 | LogPrint("payout", "[payout] block %d (%s) was orphaned -- voiding %zu" | 1735 | // Always: one line per voided entry, never a collapsed summary. |
| 1736 | // A reorg voids these in a burst from this one source line. | ||
| 1737 | LogPrintAlways("payout", "[payout] block %d (%s) was orphaned -- voiding %zu" | ||
| 1736 | " payment(s) totalling %s\n", | 1738 | " payment(s) totalling %s\n", |
| 1737 | it->blockHeight, it->blockHash.ToString().substr(0, 14).c_str(), | 1739 | it->blockHeight, it->blockHash.ToString().substr(0, 14).c_str(), |
| 1738 | it->amounts.size(), FormatMoney(total).c_str()); | 1740 | it->amounts.size(), FormatMoney(total).c_str()); |
| @@ -2036,7 +2038,7 @@ static void PayoutThreadFn(void*) | |||
| 2036 | // funding block can land at any point in that window. Upstream has | 2038 | // funding block can land at any point in that window. Upstream has |
| 2037 | // no equivalent -- it decides once, when the entry matures. | 2039 | // no equivalent -- it decides once, when the entry matures. |
| 2038 | if (!PayoutBlockStillOnChain(r.blockHash)) { | 2040 | if (!PayoutBlockStillOnChain(r.blockHash)) { |
| 2039 | LogPrint("payout", "[payout] block %d (%s) orphaned while queued" | 2041 | LogPrintAlways("payout", "[payout] block %d (%s) orphaned while queued" |
| 2040 | " -- dropping %s to %s\n", | 2042 | " -- dropping %s to %s\n", |
| 2041 | r.blockHeight, r.blockHash.ToString().substr(0, 14).c_str(), | 2043 | r.blockHeight, r.blockHash.ToString().substr(0, 14).c_str(), |
| 2042 | FormatMoney(r.amount).c_str(), r.address.c_str()); | 2044 | FormatMoney(r.amount).c_str(), r.address.c_str()); |
| @@ -2205,7 +2207,7 @@ static void PayoutThreadFn(void*) | |||
| 2205 | // described by at least one of them at every instant. | 2207 | // described by at least one of them at every instant. |
| 2206 | RecordPayoutTxid(r, wtx.GetHash().GetHex()); | 2208 | RecordPayoutTxid(r, wtx.GetHash().GetHex()); |
| 2207 | ForgetPayment(r); | 2209 | ForgetPayment(r); |
| 2208 | LogPrint("payout", "[payout] paid %s to %s (tx %s)\n", | 2210 | LogPrintAlways("payout", "[payout] paid %s to %s (tx %s)\n", |
| 2209 | FormatMoney(r.amount).c_str(), r.address.c_str(), | 2211 | FormatMoney(r.amount).c_str(), r.address.c_str(), |
| 2210 | wtx.GetHash().ToString().substr(0, 12).c_str()); | 2212 | wtx.GetHash().ToString().substr(0, 12).c_str()); |
| 2211 | } else { | 2213 | } else { |
| @@ -2263,7 +2265,7 @@ static void PayoutThreadFn(void*) | |||
| 2263 | // going away must not turn the log into one line a minute | 2265 | // going away must not turn the log into one line a minute |
| 2264 | // forever, and must not go quiet either. | 2266 | // forever, and must not go quiet either. |
| 2265 | if (r.retries <= 5 || r.retries % 60 == 0) | 2267 | if (r.retries <= 5 || r.retries % 60 == 0) |
| 2266 | LogPrint("payout", "[payout] FAILED to pay %s to %s" | 2268 | LogPrintAlways("payout", "[payout] FAILED to pay %s to %s" |
| 2267 | " (%s spendable, wallet unlocked, block %d on" | 2269 | " (%s spendable, wallet unlocked, block %d on" |
| 2268 | " the main chain) -- attempt %d, retrying in" | 2270 | " the main chain) -- attempt %d, retrying in" |
| 2269 | " 60s; this debt stands until it is paid\n", | 2271 | " 60s; this debt stands until it is paid\n", |
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 | ||
| @@ -106,15 +106,29 @@ extern bool fDebug; | |||
| 106 | // 2. Burst collapse: repeated LogPrint() calls from the *same source line* | 106 | // 2. Burst collapse: repeated LogPrint() calls from the *same source line* |
| 107 | // only emit one line per DEDUP_WINDOW_SECS; if more calls land inside | 107 | // only emit one line per DEDUP_WINDOW_SECS; if more calls land inside |
| 108 | // that window they're counted instead of reprinted, and the count is | 108 | // that window they're counted instead of reprinted, and the count is |
| 109 | // flushed as a single "(xN similar in Ws)" line the moment the window | 109 | // flushed as a single "(xN similar in Ws)" line once the window expires |
| 110 | // rolls over. A loop discovering 500 peers by address now produces a | 110 | // -- whether or not that call site ever fires again. A loop discovering |
| 111 | // small, bounded number of lines instead of 500 -- log growth is capped | 111 | // 500 peers by address now produces a small, bounded number of lines |
| 112 | // by elapsed time, not by how many similar events happen to fire. | 112 | // instead of 500 -- log growth is capped by elapsed time, not by how |
| 113 | // many similar events happen to fire. | ||
| 114 | // | ||
| 115 | // Use LogPrintAlways() instead for anything that reports an irreversible | ||
| 116 | // change to money: an obligation voided, a payment sent, a payment that | ||
| 117 | // failed. One line standing in for an unknown number of identical events is | ||
| 118 | // a tidy report of peer chatter and a false report of a ledger. A reorg | ||
| 119 | // voiding 100 entries hits one source line 100 times inside one window; | ||
| 120 | // under LogPrint() that read as a single block losing a single 49.49 BTF | ||
| 121 | // payment, while 4,950 BTF of obligations were actually destroyed, and the | ||
| 122 | // discrepancy was only found by counting entries in pending_payouts.json | ||
| 123 | // (issue #3). Bounded log growth is not worth that ambiguity here. | ||
| 113 | // --------------------------------------------------------------------------- | 124 | // --------------------------------------------------------------------------- |
| 114 | bool LogAcceptsCategory(const char* category); | 125 | bool LogAcceptsCategory(const char* category); |
| 115 | void LogPrintDedup(const char* category, const char* file, int line, const std::string& msg); | 126 | void LogPrintDedup(const char* category, const char* file, int line, const std::string& msg); |
| 127 | void LogPrintNoCollapse(const char* category, const std::string& msg); | ||
| 116 | #define LogPrint(category, ...) \ | 128 | #define LogPrint(category, ...) \ |
| 117 | do { if (LogAcceptsCategory(category)) LogPrintDedup((category), __FILE__, __LINE__, strprintf(__VA_ARGS__)); } while (0) | 129 | do { if (LogAcceptsCategory(category)) LogPrintDedup((category), __FILE__, __LINE__, strprintf(__VA_ARGS__)); } while (0) |
| 130 | #define LogPrintAlways(category, ...) \ | ||
| 131 | do { if (LogAcceptsCategory(category)) LogPrintNoCollapse((category), strprintf(__VA_ARGS__)); } while (0) | ||
| 118 | 132 | ||
| 119 | // Every format string in this project goes through one of the four functions | 133 | // Every format string in this project goes through one of the four functions |
| 120 | // below -- `printf` itself is remapped to OutputDebugStringF just above, and | 134 | // below -- `printf` itself is remapped to OutputDebugStringF just above, and |