Introduce a new RPC, getnetmsgstats
to retrieve network message statistics. This work addresses #26337. More information on the RPC design and implementation can be found in that issue.
Massive thank-you to amitiuttarwar, vasild, and ajtowns for their help on this :pray: Over the course of several months, they have patiently provided a tremendous amount of guidance and assistance in more ways than I can count!
getnetmsgstats RPC
Returns the message count and total number of bytes for sent and received network traffic. Results may optionally be arranged by direction, network, connection type and/or message type.
Arguments
show_only: an optional array of one or more dimensions to show as part of the results. Valid options are: direction, network, message_type, and connection_type. If no dimensions are specified, totals are returned.
Examples
Below are some examples on how the new getnetmsgstats
RPC can be used.
0 ./src/bitcoin-cli getnetmsgstats
1{
2 "totals": {
3 "message_count": 1905,
4 "byte_count": 818941962
5 }
6}
0./src/bitcoin-cli getnetmsgstats '["conntype","msgtype"]'
1{
2 "block-relay-only": {
3 "addrv2": {
4 "message_count": 1,
5 "byte_count": 40
6 },
7 "block": {
8 "message_count": 6,
9 "byte_count": 9899426
10 },
11 "cmpctblock": {
12 "message_count": 1,
13 "byte_count": 16615
14 },
15 "feefilter": {
16 "message_count": 1,
17 "byte_count": 32
18 },
19 "getdata": {
20 "message_count": 1,
21 "byte_count": 241
22 },
23 "getheaders": {
24 "message_count": 4,
25 "byte_count": 4212
26 },
27 "headers": {
28 "message_count": 10,
29 "byte_count": 1303
30 },
31 "inv": {
32 "message_count": 6,
33 "byte_count": 366
34 },
35 "ping": {
36 "message_count": 4,
37 "byte_count": 128
38 },
39 "pong": {
40 "message_count": 4,
41 "byte_count": 128
42 },
43 "sendaddrv2": {
44 "message_count": 4,
45 "byte_count": 96
46 },
47 "sendcmpct": {
48 "message_count": 6,
49 "byte_count": 198
50 },
51 "sendheaders": {
52 "message_count": 4,
53 "byte_count": 96
54 },
55 "verack": {
56 "message_count": 4,
57 "byte_count": 96
58 },
59 "version": {
60 "message_count": 4,
61 "byte_count": 507
62 },
63 "wtxidrelay": {
64 "message_count": 4,
65 "byte_count": 96
66 }
67 },
68 "outbound-full-relay": {
69 "addr": {
70 "message_count": 6,
71 "byte_count": 30302
72 },
73 "addrv2": {
74 "message_count": 10,
75 "byte_count": 76016
76 },
77 "blocktxn": {
78 "message_count": 1,
79 "byte_count": 1288086
80 },
81 "cmpctblock": {
82 "message_count": 1,
83 "byte_count": 16615
84 },
85 "feefilter": {
86 "message_count": 15,
87 "byte_count": 480
88 },
89 "getaddr": {
90 "message_count": 8,
91 "byte_count": 192
92 },
93 "getblocktxn": {
94 "message_count": 1,
95 "byte_count": 2515
96 },
97 "getdata": {
98 "message_count": 79,
99 "byte_count": 16951
100 },
101 "getheaders": {
102 "message_count": 15,
103 "byte_count": 15795
104 },
105 "headers": {
106 "message_count": 20,
107 "byte_count": 2039
108 },
109 "inv": {
110 "message_count": 134,
111 "byte_count": 58826
112 },
113 "notfound": {
114 "message_count": 7,
115 "byte_count": 787
116 },
117 "other": {
118 "message_count": 6,
119 "byte_count": 438
120 },
121 "ping": {
122 "message_count": 15,
123 "byte_count": 480
124 },
125 "pong": {
126 "message_count": 14,
127 "byte_count": 448
128 },
129 "sendaddrv2": {
130 "message_count": 10,
131 "byte_count": 240
132 },
133 "sendcmpct": {
134 "message_count": 19,
135 "byte_count": 627
136 },
137 "sendheaders": {
138 "message_count": 14,
139 "byte_count": 336
140 },
141 "tx": {
142 "message_count": 398,
143 "byte_count": 211333
144 },
145 "verack": {
146 "message_count": 16,
147 "byte_count": 384
148 },
149 "version": {
150 "message_count": 17,
151 "byte_count": 2151
152 },
153 "wtxidrelay": {
154 "message_count": 10,
155 "byte_count": 240
156 }
157 }
158}
0./src/bitcoin-cli getnetmsgstats '["network", "direction", "connection_type", "message_type"]'
1{
2 "ipv4": {
3 "received": {
4 "block-relay-only": {
5 "addrv2": {
6 "message_count": 5,
7 "byte_count": 227
8 },
9 "block": {
10 "message_count": 6,
11 "byte_count": 9899426
12 },
13 "cmpctblock": {
14 "message_count": 2,
15 "byte_count": 25184
16 },
17 "feefilter": {
18 "message_count": 1,
19 "byte_count": 32
20 },
21 "getheaders": {
22 "message_count": 2,
23 "byte_count": 2106
24 },
25 "headers": {
26 "message_count": 6,
27 "byte_count": 1041
28 },
29 "inv": {
30 "message_count": 3,
31 "byte_count": 183
32 },
33 "ping": {
34 "message_count": 6,
35 "byte_count": 192
36 },
37 "pong": {
38 "message_count": 6,
39 "byte_count": 192
40 },
41 "sendaddrv2": {
42 "message_count": 2,
43 "byte_count": 48
44 },
45 "sendcmpct": {
46 "message_count": 3,
47 "byte_count": 99
48 },
49 "sendheaders": {
50 "message_count": 2,
51 "byte_count": 48
52 },
53 "verack": {
54 "message_count": 2,
55 "byte_count": 48
56 },
57 "version": {
58 "message_count": 2,
59 "byte_count": 253
60 },
61 "wtxidrelay": {
62 "message_count": 2,
63 "byte_count": 48
64 }
65 },
66 "outbound-full-relay": {
67 "addr": {
68 "message_count": 4,
69 "byte_count": 30222
70 },
71 "addrv2": {
72 "message_count": 26,
73 "byte_count": 148422
74 },
75 "blocktxn": {
76 "message_count": 2,
77 "byte_count": 3752987
78 },
79 "cmpctblock": {
80 "message_count": 2,
81 "byte_count": 25184
82 },
83 "feefilter": {
84 "message_count": 11,
85 "byte_count": 352
86 },
87 "getdata": {
88 "message_count": 24,
89 "byte_count": 2184
90 },
91 "getheaders": {
92 "message_count": 11,
93 "byte_count": 11583
94 },
95 "headers": {
96 "message_count": 20,
97 "byte_count": 2120
98 },
99 "inv": {
100 "message_count": 275,
101 "byte_count": 116207
102 },
103 "notfound": {
104 "message_count": 9,
105 "byte_count": 981
106 },
107 "other": {
108 "message_count": 44,
109 "byte_count": 3430
110 },
111 "ping": {
112 "message_count": 20,
113 "byte_count": 640
114 },
115 "pong": {
116 "message_count": 20,
117 "byte_count": 640
118 },
119 "sendaddrv2": {
120 "message_count": 9,
121 "byte_count": 216
122 },
123 "sendcmpct": {
124 "message_count": 18,
125 "byte_count": 594
126 },
127 "sendheaders": {
128 "message_count": 11,
129 "byte_count": 264
130 },
131 "tx": {
132 "message_count": 1161,
133 "byte_count": 596142
134 },
135 "verack": {
136 "message_count": 12,
137 "byte_count": 288
138 },
139 "version": {
140 "message_count": 12,
141 "byte_count": 1536
142 },
143 "wtxidrelay": {
144 "message_count": 9,
145 "byte_count": 216
146 }
147 }
148 },
149 "sent": {
150 "block-relay-only": {
151 "getdata": {
152 "message_count": 1,
153 "byte_count": 241
154 },
155 "getheaders": {
156 "message_count": 2,
157 "byte_count": 2106
158 },
159 "headers": {
160 "message_count": 6,
161 "byte_count": 474
162 },
163 "inv": {
164 "message_count": 3,
165 "byte_count": 183
166 },
167 "ping": {
168 "message_count": 6,
169 "byte_count": 192
170 },
171 "pong": {
172 "message_count": 6,
173 "byte_count": 192
174 },
175 "sendaddrv2": {
176 "message_count": 2,
177 "byte_count": 48
178 },
179 "sendcmpct": {
180 "message_count": 3,
181 "byte_count": 99
182 },
183 "sendheaders": {
184 "message_count": 2,
185 "byte_count": 48
186 },
187 "verack": {
188 "message_count": 2,
189 "byte_count": 48
190 },
191 "version": {
192 "message_count": 2,
193 "byte_count": 254
194 },
195 "wtxidrelay": {
196 "message_count": 2,
197 "byte_count": 48
198 }
199 },
200 "outbound-full-relay": {
201 "addr": {
202 "message_count": 4,
203 "byte_count": 250
204 },
205 "addrv2": {
206 "message_count": 19,
207 "byte_count": 938
208 },
209 "feefilter": {
210 "message_count": 12,
211 "byte_count": 384
212 },
213 "getaddr": {
214 "message_count": 12,
215 "byte_count": 288
216 },
217 "getblocktxn": {
218 "message_count": 2,
219 "byte_count": 3883
220 },
221 "getdata": {
222 "message_count": 249,
223 "byte_count": 48813
224 },
225 "getheaders": {
226 "message_count": 12,
227 "byte_count": 12636
228 },
229 "headers": {
230 "message_count": 13,
231 "byte_count": 1297
232 },
233 "inv": {
234 "message_count": 464,
235 "byte_count": 166868
236 },
237 "ping": {
238 "message_count": 21,
239 "byte_count": 672
240 },
241 "pong": {
242 "message_count": 20,
243 "byte_count": 640
244 },
245 "sendaddrv2": {
246 "message_count": 9,
247 "byte_count": 216
248 },
249 "sendcmpct": {
250 "message_count": 13,
251 "byte_count": 429
252 },
253 "sendheaders": {
254 "message_count": 11,
255 "byte_count": 264
256 },
257 "tx": {
258 "message_count": 44,
259 "byte_count": 18966
260 },
261 "verack": {
262 "message_count": 12,
263 "byte_count": 288
264 },
265 "version": {
266 "message_count": 13,
267 "byte_count": 1651
268 },
269 "wtxidrelay": {
270 "message_count": 9,
271 "byte_count": 216
272 }
273 }
274 }
275 }
276}
Things to consider
RPC behavior: Should we allow dimensions to be rearraged?
When it comes time to gather up the RPC results, @vasild has provided an alternate implementation that uses an array instead of the MultiMap
structure. This results in two changes:
- using the stack over the heap (yay!)
- enforcing a strict ordering of dimensions (direction, network, connection type, message type)
Aside from being good for memory, the reasoning here is allowing users to rearrange dimensions may be too confusing. I personally really like the ability to rearrange dimensions, which is why I have not integrated that solution into this PR, but am open to whatever the larger community prefers :)
Locking & Consistency Beyond Individual Values
With atomics, we know we can rely on the values for individual stats like bye count and message count. However, the byte count and message count may not always match. For example, let’s say we currently have 5 messages, and 100 bytes:
1. A new 20 byte message comes in. First the byte count is incremented to 120.
2. A request to read the stats comes in. The RPC returns message count 5 and byte count 120.
3. The message count is incremented to 6 in response to the new message that came in at step 1.
The read operation did not return accurate data! Unlike the torn write example for a single value, It returned data that was true for some point in time, it’s just that the values for message count and byte count were inconsistent.
To solve this, it’s actually not enough to lock the MsgStat
struct. It’s my understanding that you need a mutex to protect the entire NetStats
data structure.
The trade off here isn’t attractive. A lock would halt network threads that are doing important stuff, all for the sake of consistent stats. Even for reads, we would have to stop writes. We’d end up stopping threads that are doing important things for something that is not that important.
Another thing to consider is how often will this RPC be called? If it’s once in a blue moon, then such a mutex is probably fine. But for a live dashboard that is making a call every second, this is not acceptable.
Making Enum Indices Safe and Reliable
src/net.cpp
contains a bunch of *To/FromIndex()
methods that convert an enum to an index number. I’ve decided to be explicit about these conversions because enums are not guaranteed to start at zero and it’s not enough to simply associate the first value in an enum with a number.
To protect against potential gaps or overlaps in numbering, every single enum value must be assigned an index. This is the only way to guarantee that the numbering is safe and reliable.
Instead of updating the existing Network
and ConnectionType
enums to assign indices to each enum value, and risk unintentionally modifying the behavior of code that uses these enums, I’ve opted for the conversion methods. This also narrows the scope of the conversion methods, making changed behavior easier to spot if the indices are modified.
I’m interested in feedback on this. The *To/FromIndex()
methods have low review and maintenance cost, but I’m unsure if I’ve correctly evaluated the risks associated with updating the Network
and ConnectionType
enums . Also open to discussion on if there is a better place for these conversion methods to live.