Skip to content

Commit 19f2777

Browse files
committed
Merge bitcoin/bitcoin#26593: tracing: Only prepare tracepoint arguments when actually tracing
0de3e96 tracing: use bitcoind pid in bcc tracing examples (0xb10c) 411c6cf tracing: only prepare tracepoint args if attached (0xb10c) d524c1e tracing: dedup TRACE macros & rename to TRACEPOINT (0xb10c) Pull request description: Currently, if the tracepoints are compiled (e.g. in depends and release builds), we always prepare the tracepoint arguments regardless of the tracepoints being used or not. We made sure that the argument preparation is as cheap as possible, but we can almost completely eliminate any overhead for users not interested in the tracepoints (the vast majority), by gating the tracepoint argument preparation with an `if(something is attached to this tracepoint)`. To achieve this, we use the optional semaphore feature provided by SystemTap. The first commit simplifies and deduplicates our tracepoint macros from 13 TRACEx macros to a single TRACEPOINT macro. This makes them easier to use and also avoids more duplicate macro definitions in the second commit. The Linux tracing tools I'm aware of (bcc, bpftrace, libbpf, and systemtap) all support the semaphore gating feature. Thus, all existing tracepoints and their argument preparation is gated in the second commit. For details, please refer to the commit messages and the updated documentation in `doc/tracing.md`. Also adding unit tests that include all tracepoint macros to make sure there are no compiler problems with them (e.g. some varadiac extension not supported). Reviewers might want to check: - Do the tracepoints still work for you? Do the examples in `contrib/tracing/` run on your system (as bpftrace frequently breaks on every new version, please test master too if it should't work for you)? Do the CI interface tests still pass? - Is the new documentation clear? - The `TRACEPOINT_SEMAPHORE(event, context)` macros places global variables in our global namespace. Is this something we strictly want to avoid or maybe move to all `TRACEPOINT_SEMAPHORE`s to a separate .cpp file or even namespace? I like having the `TRACEPOINT_SEMAPHORE()` in same file as the `TRACEPOINT()`, but open for suggestion on alternative approaches. - Are newly added tracepoints in the unit tests visible when using `readelf -n build/src/test/test_bitcoin`? You can run the new unit tests with `./build/src/test/test_bitcoin --run_test=util_trace_tests* --log_level=all`. <details><summary>Two of the added unit tests demonstrate that we are only processing the tracepoint arguments when attached by having a test-failure condition in the tracepoint argument preparation. The following bpftrace script can be used to demonstrate that the tests do indeed fail when attached to the tracepoints.</summary> `fail_tests.bt`: ```c #!/usr/bin/env bpftrace usdt:./build/src/test/test_bitcoin:test:check_if_attached { printf("the 'check_if_attached' test should have failed\n"); } usdt:./build/src/test/test_bitcoin:test:expensive_section { printf("the 'expensive_section' test should have failed\n"); } ``` Run the unit tests with `./build/src/test/test_bitcoin` and start `bpftrace fail_tests.bt -p $(pidof test_bitcoin)` in a separate terminal. The unit tests should fail with: ``` Running 594 test cases... test/util_trace_tests.cpp(31): error: in "util_trace_tests/test_tracepoint_check_if_attached": check false has failed test/util_trace_tests.cpp(51): error: in "util_trace_tests/test_tracepoint_manual_tracepoint_active_check": check false has failed *** 2 failures are detected in the test module "Bitcoin Core Test Suite" ``` </details> These links might provide more contextual information for reviewers: - [How SystemTap Userspace Probes Work by eklitzke](https://eklitzke.org/how-sytemtap-userspace-probes-work) (actually an example on Bitcoin Core; mentions that with semaphores "the overhead for an untraced process is effectively zero.") - [libbpf comment on USDT semaphore handling](https://github.com/libbpf/libbpf/blob/1596a09b5de2a50ab8d44218fc29b6d42f886305/src/usdt.c#L83-L92) (can recommend the whole comment for background on how the tracepoints and tracing tools work together) - https://sourceware.org/systemtap/wiki/UserSpaceProbeImplementation#Semaphore_Handling ACKs for top commit: willcl-ark: utACK 0de3e96 laanwj: re-ACK 0de3e96 jb55: utACK 0de3e96 vasild: ACK 0de3e96 Tree-SHA512: 0e5e0dc5e0353beaf5c446e4be03d447e64228b1be71ee9972fde1d6fac3fac71a9d73c6ce4fa68975f87db2b2bf6eee2009921a2a145e24d83a475d007a559b
2 parents 0903ce8 + 0de3e96 commit 19f2777

16 files changed

+222
-119
lines changed

cmake/module/FindUSDT.cmake

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,16 @@ if(USDT_INCLUDE_DIR)
3636
include(CheckCXXSourceCompiles)
3737
set(CMAKE_REQUIRED_INCLUDES ${USDT_INCLUDE_DIR})
3838
check_cxx_source_compiles("
39+
// Setting SDT_USE_VARIADIC lets systemtap (sys/sdt.h) know that we want to use
40+
// the optional variadic macros to define tracepoints.
41+
#define SDT_USE_VARIADIC 1
3942
#include <sys/sdt.h>
4043
4144
int main()
4245
{
43-
DTRACE_PROBE(context, event);
46+
STAP_PROBEV(context, event);
4447
int a, b, c, d, e, f, g;
45-
DTRACE_PROBE7(context, event, a, b, c, d, e, f, g);
48+
STAP_PROBEV(context, event, a, b, c, d, e, f, g);
4649
}
4750
" HAVE_USDT_H
4851
)

contrib/tracing/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ about the connection. Peers can be selected individually to view recent P2P
8282
messages.
8383

8484
```
85-
$ python3 contrib/tracing/p2p_monitor.py ./build/src/bitcoind
85+
$ python3 contrib/tracing/p2p_monitor.py $(pidof bitcoind)
8686
```
8787

8888
Lists selectable peers and traffic and connection information.
@@ -150,7 +150,7 @@ lost. BCC prints: `Possibly lost 2 samples` on lost messages.
150150

151151

152152
```
153-
$ python3 contrib/tracing/log_raw_p2p_msgs.py ./build/src/bitcoind
153+
$ python3 contrib/tracing/log_raw_p2p_msgs.py $(pidof bitcoind)
154154
```
155155

156156
```
@@ -241,7 +241,7 @@ A BCC Python script to log the UTXO cache flushes. Based on the
241241
`utxocache:flush` tracepoint.
242242

243243
```bash
244-
$ python3 contrib/tracing/log_utxocache_flush.py ./build/src/bitcoind
244+
$ python3 contrib/tracing/log_utxocache_flush.py $(pidof bitcoind)
245245
```
246246

247247
```
@@ -300,7 +300,7 @@ comprising a timestamp along with all event data available via the event's
300300
tracepoint.
301301

302302
```console
303-
$ python3 contrib/tracing/mempool_monitor.py ./build/src/bitcoind
303+
$ python3 contrib/tracing/mempool_monitor.py $(pidof bitcoind)
304304
```
305305

306306
```

contrib/tracing/log_raw_p2p_msgs.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,9 @@ def print_message(event, inbound):
132132
)
133133

134134

135-
def main(bitcoind_path):
136-
bitcoind_with_usdts = USDT(path=str(bitcoind_path))
135+
def main(pid):
136+
print(f"Hooking into bitcoind with pid {pid}")
137+
bitcoind_with_usdts = USDT(pid=int(pid))
137138

138139
# attaching the trace functions defined in the BPF program to the tracepoints
139140
bitcoind_with_usdts.enable_probe(
@@ -176,8 +177,8 @@ def handle_outbound(_, data, size):
176177

177178

178179
if __name__ == "__main__":
179-
if len(sys.argv) < 2:
180-
print("USAGE:", sys.argv[0], "path/to/bitcoind")
180+
if len(sys.argv) != 2:
181+
print("USAGE:", sys.argv[0], "<pid of bitcoind>")
181182
exit()
182-
path = sys.argv[1]
183-
main(path)
183+
pid = sys.argv[1]
184+
main(pid)

contrib/tracing/log_utxocache_flush.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,9 @@ def print_event(event):
7070
))
7171

7272

73-
def main(bitcoind_path):
74-
bitcoind_with_usdts = USDT(path=str(bitcoind_path))
73+
def main(pid):
74+
print(f"Hooking into bitcoind with pid {pid}")
75+
bitcoind_with_usdts = USDT(pid=int(pid))
7576

7677
# attaching the trace functions defined in the BPF program
7778
# to the tracepoints
@@ -99,9 +100,9 @@ def handle_flush(_, data, size):
99100

100101

101102
if __name__ == "__main__":
102-
if len(sys.argv) < 2:
103-
print("USAGE: ", sys.argv[0], "path/to/bitcoind")
103+
if len(sys.argv) != 2:
104+
print("USAGE: ", sys.argv[0], "<pid of bitcoind>")
104105
exit(1)
105106

106-
path = sys.argv[1]
107-
main(path)
107+
pid = sys.argv[1]
108+
main(pid)

contrib/tracing/mempool_monitor.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,9 @@
114114
"""
115115

116116

117-
def main(bitcoind_path):
118-
bitcoind_with_usdts = USDT(path=str(bitcoind_path))
117+
def main(pid):
118+
print(f"Hooking into bitcoind with pid {pid}")
119+
bitcoind_with_usdts = USDT(pid=int(pid))
119120

120121
# attaching the trace functions defined in the BPF program
121122
# to the tracepoints
@@ -365,8 +366,8 @@ def timestamp_age(timestamp):
365366

366367
if __name__ == "__main__":
367368
if len(sys.argv) < 2:
368-
print("USAGE: ", sys.argv[0], "path/to/bitcoind")
369+
print("USAGE: ", sys.argv[0], "<pid of bitcoind>")
369370
exit(1)
370371

371-
path = sys.argv[1]
372-
main(path)
372+
pid = sys.argv[1]
373+
main(pid)

contrib/tracing/p2p_monitor.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
# outbound P2P messages. The eBPF program submits the P2P messages to
1515
# this script via a BPF ring buffer.
1616

17-
import sys
1817
import curses
18+
import os
19+
import sys
1920
from curses import wrapper, panel
2021
from bcc import BPF, USDT
2122

@@ -115,10 +116,10 @@ def add_message(self, message):
115116
self.total_outbound_msgs += 1
116117

117118

118-
def main(bitcoind_path):
119+
def main(pid):
119120
peers = dict()
120-
121-
bitcoind_with_usdts = USDT(path=str(bitcoind_path))
121+
print(f"Hooking into bitcoind with pid {pid}")
122+
bitcoind_with_usdts = USDT(pid=int(pid))
122123

123124
# attaching the trace functions defined in the BPF program to the tracepoints
124125
bitcoind_with_usdts.enable_probe(
@@ -245,9 +246,14 @@ def render(screen, peers, cur_list_pos, scroll, ROWS_AVALIABLE_FOR_LIST, info_pa
245246
(msg.msg_type, msg.size), curses.A_NORMAL)
246247

247248

249+
def running_as_root():
250+
return os.getuid() == 0
251+
248252
if __name__ == "__main__":
249-
if len(sys.argv) < 2:
250-
print("USAGE:", sys.argv[0], "path/to/bitcoind")
253+
if len(sys.argv) != 2:
254+
print("USAGE:", sys.argv[0], "<pid of bitcoind>")
251255
exit()
252-
path = sys.argv[1]
253-
main(path)
256+
if not running_as_root():
257+
print("You might not have the privileges required to hook into the tracepoints!")
258+
pid = sys.argv[1]
259+
main(pid)

doc/tracing.md

Lines changed: 49 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -265,42 +265,52 @@ Arguments passed:
265265

266266
## Adding tracepoints to Bitcoin Core
267267

268-
To add a new tracepoint, `#include <util/trace.h>` in the compilation unit where
269-
the tracepoint is inserted. Use one of the `TRACEx` macros listed below
270-
depending on the number of arguments passed to the tracepoint. Up to 12
271-
arguments can be provided. The `context` and `event` specify the names by which
272-
the tracepoint is referred to. Please use `snake_case` and try to make sure that
273-
the tracepoint names make sense even without detailed knowledge of the
274-
implementation details. Do not forget to update the tracepoint list in this
275-
document.
276-
277-
```c
278-
#define TRACE(context, event)
279-
#define TRACE1(context, event, a)
280-
#define TRACE2(context, event, a, b)
281-
#define TRACE3(context, event, a, b, c)
282-
#define TRACE4(context, event, a, b, c, d)
283-
#define TRACE5(context, event, a, b, c, d, e)
284-
#define TRACE6(context, event, a, b, c, d, e, f)
285-
#define TRACE7(context, event, a, b, c, d, e, f, g)
286-
#define TRACE8(context, event, a, b, c, d, e, f, g, h)
287-
#define TRACE9(context, event, a, b, c, d, e, f, g, h, i)
288-
#define TRACE10(context, event, a, b, c, d, e, f, g, h, i, j)
289-
#define TRACE11(context, event, a, b, c, d, e, f, g, h, i, j, k)
290-
#define TRACE12(context, event, a, b, c, d, e, f, g, h, i, j, k, l)
291-
```
268+
Use the `TRACEPOINT` macro to add a new tracepoint. If not yet included, include
269+
`util/trace.h` (defines the tracepoint macros) with `#include <util/trace.h>`.
270+
Each tracepoint needs a `context` and an `event`. Please use `snake_case` and
271+
try to make sure that the tracepoint names make sense even without detailed
272+
knowledge of the implementation details. You can pass zero to twelve arguments
273+
to the tracepoint. Each tracepoint also needs a global semaphore. The semaphore
274+
gates the tracepoint arguments from being processed if we are not attached to
275+
the tracepoint. Add a `TRACEPOINT_SEMAPHORE(context, event)` with the `context`
276+
and `event` of your tracepoint in the top-level namespace at the beginning of
277+
the file. Do not forget to update the tracepoint list in this document.
278+
279+
For example, the `net:outbound_message` tracepoint in `src/net.cpp` with six
280+
arguments.
292281

293-
For example:
282+
```C++
283+
// src/net.cpp
284+
TRACEPOINT_SEMAPHORE(net, outbound_message);
285+
286+
void CConnman::PushMessage(…) {
287+
288+
TRACEPOINT(net, outbound_message,
289+
pnode->GetId(),
290+
pnode->m_addr_name.c_str(),
291+
pnode->ConnectionTypeAsString().c_str(),
292+
sanitizedType.c_str(),
293+
msg.data.size(),
294+
msg.data.data()
295+
);
296+
297+
}
298+
```
299+
If needed, an extra `if (TRACEPOINT_ACTIVE(context, event)) {...}` check can be
300+
used to prepare somewhat expensive arguments right before the tracepoint. While
301+
the tracepoint arguments are only prepared when we attach something to the
302+
tracepoint, an argument preparation should never hang the process. Hashing and
303+
serialization of data structures is probably fine, a `sleep(10s)` not.
294304
295305
```C++
296-
TRACE6(net, inbound_message,
297-
pnode->GetId(),
298-
pnode->m_addr_name.c_str(),
299-
pnode->ConnectionTypeAsString().c_str(),
300-
sanitizedType.c_str(),
301-
msg.data.size(),
302-
msg.data.data()
303-
);
306+
// An example tracepoint with an expensive argument.
307+
308+
TRACEPOINT_SEMAPHORE(example, gated_expensive_argument);
309+
310+
if (TRACEPOINT_ACTIVE(example, gated_expensive_argument)) {
311+
expensive_argument = expensive_calulation();
312+
TRACEPOINT(example, gated_expensive_argument, expensive_argument);
313+
}
304314
```
305315

306316
### Guidelines and best practices
@@ -318,12 +328,6 @@ the tracepoint. See existing examples in [contrib/tracing/].
318328

319329
[contrib/tracing/]: ../contrib/tracing/
320330

321-
#### No expensive computations for tracepoints
322-
Data passed to the tracepoint should be inexpensive to compute. Although the
323-
tracepoint itself only has overhead when enabled, the code to compute arguments
324-
is always run - even if the tracepoint is not used. For example, avoid
325-
serialization and parsing.
326-
327331
#### Semi-stable API
328332
Tracepoints should have a semi-stable API. Users should be able to rely on the
329333
tracepoints for scripting. This means tracepoints need to be documented, and the
@@ -347,7 +351,7 @@ first six argument fields. Binary data can be placed in later arguments. The BCC
347351
supports reading from all 12 arguments.
348352

349353
#### Strings as C-style String
350-
Generally, strings should be passed into the `TRACEx` macros as pointers to
354+
Generally, strings should be passed into the `TRACEPOINT` macros as pointers to
351355
C-style strings (a null-terminated sequence of characters). For C++
352356
`std::strings`, [`c_str()`] can be used. It's recommended to document the
353357
maximum expected string size if known.
@@ -370,9 +374,9 @@ $ gdb ./build/src/bitcoind
370374
371375
(gdb) info probes
372376
Type Provider Name Where Semaphore Object
373-
stap net inbound_message 0x000000000014419e /build/src/bitcoind
374-
stap net outbound_message 0x0000000000107c05 /build/src/bitcoind
375-
stap validation block_connected 0x00000000002fb10c /build/src/bitcoind
377+
stap net inbound_message 0x000000000014419e 0x0000000000d29bd2 /build/src/bitcoind
378+
stap net outbound_message 0x0000000000107c05 0x0000000000d29bd0 /build/src/bitcoind
379+
stap validation block_connected 0x00000000002fb10c 0x0000000000d29bd8 /build/src/bitcoind
376380
377381
```
378382

@@ -388,7 +392,7 @@ Displaying notes found in: .note.stapsdt
388392
stapsdt 0x0000005d NT_STAPSDT (SystemTap probe descriptors)
389393
Provider: net
390394
Name: outbound_message
391-
Location: 0x0000000000107c05, Base: 0x0000000000579c90, Semaphore: 0x0000000000000000
395+
Location: 0x0000000000107c05, Base: 0x0000000000579c90, Semaphore: 0x0000000000d29bd0
392396
Arguments: -8@%r12 8@%rbx 8@%rdi 8@192(%rsp) 8@%rax 8@%rdx
393397
394398
```
@@ -407,7 +411,7 @@ between distributions. For example, on
407411

408412
```
409413
$ tplist -l ./build/src/bitcoind -v
410-
b'net':b'outbound_message' [sema 0x0]
414+
b'net':b'outbound_message' [sema 0xd29bd0]
411415
1 location(s)
412416
6 argument(s)
413417

src/coins.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
#include <random.h>
1010
#include <util/trace.h>
1111

12+
TRACEPOINT_SEMAPHORE(utxocache, add);
13+
TRACEPOINT_SEMAPHORE(utxocache, spent);
14+
TRACEPOINT_SEMAPHORE(utxocache, uncache);
15+
1216
std::optional<Coin> CCoinsView::GetCoin(const COutPoint& outpoint) const { return std::nullopt; }
1317
uint256 CCoinsView::GetBestBlock() const { return uint256(); }
1418
std::vector<uint256> CCoinsView::GetHeadBlocks() const { return std::vector<uint256>(); }
@@ -97,7 +101,7 @@ void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possi
97101
it->second.coin = std::move(coin);
98102
it->second.AddFlags(CCoinsCacheEntry::DIRTY | (fresh ? CCoinsCacheEntry::FRESH : 0), *it, m_sentinel);
99103
cachedCoinsUsage += it->second.coin.DynamicMemoryUsage();
100-
TRACE5(utxocache, add,
104+
TRACEPOINT(utxocache, add,
101105
outpoint.hash.data(),
102106
(uint32_t)outpoint.n,
103107
(uint32_t)it->second.coin.nHeight,
@@ -131,7 +135,7 @@ bool CCoinsViewCache::SpendCoin(const COutPoint &outpoint, Coin* moveout) {
131135
CCoinsMap::iterator it = FetchCoin(outpoint);
132136
if (it == cacheCoins.end()) return false;
133137
cachedCoinsUsage -= it->second.coin.DynamicMemoryUsage();
134-
TRACE5(utxocache, spent,
138+
TRACEPOINT(utxocache, spent,
135139
outpoint.hash.data(),
136140
(uint32_t)outpoint.n,
137141
(uint32_t)it->second.coin.nHeight,
@@ -278,7 +282,7 @@ void CCoinsViewCache::Uncache(const COutPoint& hash)
278282
CCoinsMap::iterator it = cacheCoins.find(hash);
279283
if (it != cacheCoins.end() && !it->second.IsDirty() && !it->second.IsFresh()) {
280284
cachedCoinsUsage -= it->second.coin.DynamicMemoryUsage();
281-
TRACE5(utxocache, uncache,
285+
TRACEPOINT(utxocache, uncache,
282286
hash.hash.data(),
283287
(uint32_t)hash.n,
284288
(uint32_t)it->second.coin.nHeight,

src/net.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@
5353
#include <optional>
5454
#include <unordered_map>
5555

56+
TRACEPOINT_SEMAPHORE(net, outbound_message);
57+
5658
/** Maximum number of block-relay-only anchor connections */
5759
static constexpr size_t MAX_BLOCK_RELAY_ONLY_ANCHORS = 2;
5860
static_assert (MAX_BLOCK_RELAY_ONLY_ANCHORS <= static_cast<size_t>(MAX_BLOCK_RELAY_ONLY_CONNECTIONS), "MAX_BLOCK_RELAY_ONLY_ANCHORS must not exceed MAX_BLOCK_RELAY_ONLY_CONNECTIONS.");
@@ -3811,7 +3813,7 @@ void CConnman::PushMessage(CNode* pnode, CSerializedNetMsg&& msg)
38113813
CaptureMessage(pnode->addr, msg.m_type, msg.data, /*is_incoming=*/false);
38123814
}
38133815

3814-
TRACE6(net, outbound_message,
3816+
TRACEPOINT(net, outbound_message,
38153817
pnode->GetId(),
38163818
pnode->m_addr_name.c_str(),
38173819
pnode->ConnectionTypeAsString().c_str(),

src/net_processing.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757

5858
using namespace util::hex_literals;
5959

60+
TRACEPOINT_SEMAPHORE(net, inbound_message);
61+
6062
/** Headers download timeout.
6163
* Timeout = base + per_header * (expected number of headers) */
6264
static constexpr auto HEADERS_DOWNLOAD_TIMEOUT_BASE = 15min;
@@ -4969,7 +4971,7 @@ bool PeerManagerImpl::ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt
49694971
CNetMessage& msg{poll_result->first};
49704972
bool fMoreWork = poll_result->second;
49714973

4972-
TRACE6(net, inbound_message,
4974+
TRACEPOINT(net, inbound_message,
49734975
pfrom->GetId(),
49744976
pfrom->m_addr_name.c_str(),
49754977
pfrom->ConnectionTypeAsString().c_str(),

0 commit comments

Comments
 (0)