upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/rpc.cpp10
-rw-r--r--src/util.cpp99
-rw-r--r--src/util.h22
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 {
47static std::map<std::string, LogDedupState> g_logDedup; 47static std::map<std::string, LogDedupState> g_logDedup;
48static CCriticalSection cs_logDedup; 48static 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.
52struct 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
59static int64 g_logDedupLastSweep = 0;
60
61static 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.
82static 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
50void LogPrintDedup(const char* category, const char* file, int line, const std::string& msgIn) 105void 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.
176void 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
diff --git a/src/util.h b/src/util.h
index d72518b..fe605a1 100644
--- a/src/util.h
+++ b/src/util.h
@@ -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// ---------------------------------------------------------------------------
114bool LogAcceptsCategory(const char* category); 125bool LogAcceptsCategory(const char* category);
115void LogPrintDedup(const char* category, const char* file, int line, const std::string& msg); 126void LogPrintDedup(const char* category, const char* file, int line, const std::string& msg);
127void 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