Line data Source code
1 : #ifndef HEADER_fd_src_disco_gui_fd_gui_peers_h
2 : #define HEADER_fd_src_disco_gui_fd_gui_peers_h
3 :
4 : /* fd_gui_peers defines methods that maintain metrics and metadata about
5 : Solana cluster peers that are active on the Gossip network.
6 :
7 : Peer identifiers are added and removed by incoming update messages
8 : from the gossip tile. Additional information about the peer is
9 : obtained from other places and merged into the a large peers table
10 : with live updates.
11 :
12 : fd_gui_peers also defines methods for handling messages from a
13 : WebSocket client. These messages contain peer related information,
14 : including a live view of the peer table with the option to order the
15 : table a custom sort key. */
16 :
17 : #include "../../util/net/fd_net_headers.h"
18 : #include "../../disco/metrics/fd_metrics.h"
19 : #include "../../flamenco/gossip/fd_gossip_types.h"
20 : #include "../../discof/replay/fd_replay_tile.h"
21 : #include "../../flamenco/runtime/fd_runtime_const.h"
22 :
23 : #include "../../waltz/http/fd_http_server.h"
24 : #include "../topo/fd_topo.h"
25 :
26 : #define FD_GUI_PEERS_VALIDATOR_INFO_NAME_SZ ( 64UL)
27 : #define FD_GUI_PEERS_VALIDATOR_INFO_WEBSITE_SZ (128UL)
28 : #define FD_GUI_PEERS_VALIDATOR_INFO_DETAILS_SZ (256UL)
29 : #define FD_GUI_PEERS_VALIDATOR_INFO_ICON_URI_SZ (128UL)
30 :
31 : #define FD_GUI_PEERS_NODE_NOP (0)
32 0 : #define FD_GUI_PEERS_NODE_ADD (1)
33 0 : #define FD_GUI_PEERS_NODE_UPDATE (2)
34 0 : #define FD_GUI_PEERS_NODE_DELETE (3)
35 :
36 0 : #define FD_GUI_PEERS_CI_TABLE_SORT_KEY_CNT (256UL) /* maximum number of maintained active sort keys */
37 0 : #define FD_GUI_PEERS_WS_VIEWPORT_MAX_SZ (200UL) /* the maximum number of rows a client can request for a table viewport */
38 0 : #define FD_GUI_PEERS_WS_VIEWPORT_UPDATE_INTERVAL_MILLIS ( 100L)
39 0 : #define FD_GUI_PEERS_METRIC_RATE_UPDATE_INTERVAL_MILLIS ( 100L)
40 0 : #define FD_GUI_PEERS_GOSSIP_STATS_UPDATE_INTERVAL_MILLIS ( 100L)
41 :
42 0 : #define FD_GUI_PEERS_GOSSIP_TOP_PEERS_CNT (64UL)
43 :
44 : /* Some table columns are rates of change, which require keeping a
45 : historical value / timestamp. */
46 : struct fd_gui_peers_metric_rate {
47 : ulong cur;
48 : ulong ref;
49 : long rate; /* units per sec. live_table treaps use this field to sort table entries */
50 : };
51 : typedef struct fd_gui_peers_metric_rate fd_gui_peers_metric_rate_t;
52 :
53 : struct fd_gui_peers_vote {
54 : fd_pubkey_t node_account;
55 : fd_pubkey_t vote_account;
56 : ulong stake;
57 : ulong last_vote_slot;
58 : long last_vote_timestamp;
59 : uchar commission;
60 : ulong epoch;
61 : ulong epoch_credits;
62 : };
63 :
64 : typedef struct fd_gui_peers_vote fd_gui_peers_vote_t;
65 :
66 : struct fd_gui_peers_node {
67 : int valid;
68 : long update_time_nanos;
69 : fd_contact_info_t contact_info;
70 :
71 : fd_gui_peers_metric_rate_t gossvf_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_CNT ];
72 : fd_gui_peers_metric_rate_t gossip_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_CNT ];
73 : fd_gui_peers_metric_rate_t gossvf_rx_sum; /* sum of gossvf_rx */
74 : fd_gui_peers_metric_rate_t gossip_tx_sum; /* sum of gossip_tx */
75 :
76 : int has_val_info;
77 : char name [ FD_GUI_PEERS_VALIDATOR_INFO_NAME_SZ ];
78 : char website [ FD_GUI_PEERS_VALIDATOR_INFO_WEBSITE_SZ ];
79 : char details [ FD_GUI_PEERS_VALIDATOR_INFO_DETAILS_SZ ];
80 : char icon_uri[ FD_GUI_PEERS_VALIDATOR_INFO_ICON_URI_SZ ];
81 :
82 : int has_vote_info;
83 : fd_pubkey_t vote_account;
84 : ulong stake;
85 : ulong last_vote_slot;
86 : long last_vote_timestamp;
87 : uchar commission;
88 : ulong epoch;
89 : ulong epoch_credits;
90 : int delinquent;
91 :
92 : struct {
93 : ulong next;
94 : ulong prev;
95 : } pubkey_map;
96 :
97 : struct {
98 : ulong next;
99 : ulong prev;
100 : } sock_map;
101 :
102 : struct {
103 : ulong parent;
104 : ulong left;
105 : ulong right;
106 : ulong prio;
107 : ulong next;
108 : ulong prev;
109 : } treaps_live_table[ FD_GUI_PEERS_CI_TABLE_SORT_KEY_CNT ];
110 : struct {
111 : ulong next;
112 : ulong prev;
113 : } dlist_live_table;
114 : ulong sort_keys_live_table;
115 :
116 : struct {
117 : ulong parent;
118 : ulong left;
119 : ulong right;
120 : ulong prio;
121 : ulong next;
122 : ulong prev;
123 : } treaps_bandwidth_tracking[ 2UL ];
124 : struct {
125 : ulong next;
126 : ulong prev;
127 : } dlist_bandwidth_tracking;
128 : ulong sort_keys_bandwidth_tracking;
129 : };
130 : typedef struct fd_gui_peers_node fd_gui_peers_node_t;
131 :
132 : struct fd_gui_peers_gossip_stats {
133 : long sample_time;
134 : ulong network_health_pull_response_msg_rx_success;
135 : ulong network_health_pull_response_msg_rx_failure;
136 : ulong network_health_push_msg_rx_success;
137 : ulong network_health_push_msg_rx_failure;
138 : ulong network_health_push_crds_rx_duplicate;
139 : ulong network_health_pull_response_crds_rx_duplicate;
140 : ulong network_health_push_crds_rx_success;
141 : ulong network_health_push_crds_rx_failure;
142 : ulong network_health_pull_response_crds_rx_success;
143 : ulong network_health_pull_response_crds_rx_failure;
144 : ulong network_health_push_msg_tx;
145 : ulong network_health_pull_response_msg_tx;
146 : ulong network_health_total_stake; /* lamports */
147 : ulong network_health_total_peers;
148 : ulong network_health_connected_stake; /* lamports */
149 : ulong network_health_connected_staked_peers;
150 : ulong network_health_connected_unstaked_peers;
151 : ulong network_ingress_total_bytes;
152 : ulong network_ingress_peer_sz;
153 : long network_ingress_peer_bytes_per_sec [ FD_GUI_PEERS_GOSSIP_TOP_PEERS_CNT ];
154 : char network_ingress_peer_names [ FD_GUI_PEERS_GOSSIP_TOP_PEERS_CNT ][ FD_GUI_PEERS_VALIDATOR_INFO_NAME_SZ ];
155 : fd_pubkey_t network_ingress_peer_identities[ FD_GUI_PEERS_GOSSIP_TOP_PEERS_CNT ];
156 : long network_ingress_total_bytes_per_sec;
157 : ulong network_egress_total_bytes;
158 : ulong network_egress_peer_sz;
159 : long network_egress_peer_bytes_per_sec [ FD_GUI_PEERS_GOSSIP_TOP_PEERS_CNT ];
160 : char network_egress_peer_names [ FD_GUI_PEERS_GOSSIP_TOP_PEERS_CNT ][ FD_GUI_PEERS_VALIDATOR_INFO_NAME_SZ ];
161 : fd_pubkey_t network_egress_peer_identities[ FD_GUI_PEERS_GOSSIP_TOP_PEERS_CNT ];
162 : long network_egress_total_bytes_per_sec;
163 : ulong storage_capacity;
164 : ulong storage_expired_cnt;
165 : ulong storage_evicted_cnt;
166 : ulong storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_CNT ];
167 : ulong storage_cnt_tx [ FD_METRICS_ENUM_CRDS_VALUE_CNT ];
168 : ulong storage_bytes_tx [ FD_METRICS_ENUM_CRDS_VALUE_CNT ];
169 : ulong messages_push_rx_cnt;
170 : ulong messages_push_tx_cnt;
171 : ulong messages_pull_response_rx_cnt;
172 : ulong messages_pull_response_tx_cnt;
173 : ulong messages_bytes_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_CNT ];
174 : ulong messages_count_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_CNT ];
175 : ulong messages_bytes_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_CNT ];
176 : ulong messages_count_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_CNT ];
177 : };
178 : typedef struct fd_gui_peers_gossip_stats fd_gui_peers_gossip_stats_t;
179 :
180 : #define MAP_NAME fd_gui_peers_node_pubkey_map
181 0 : #define MAP_ELE_T fd_gui_peers_node_t
182 : #define MAP_KEY_T fd_pubkey_t
183 0 : #define MAP_KEY contact_info.pubkey
184 0 : #define MAP_IDX_T ulong
185 0 : #define MAP_NEXT pubkey_map.next
186 0 : #define MAP_PREV pubkey_map.prev
187 0 : #define MAP_KEY_HASH(k,s) ((s) ^ fd_ulong_hash( (k)->ul[ 0 ] ))
188 0 : #define MAP_KEY_EQ(k0,k1) (!memcmp((k0)->uc, (k1)->uc, 32UL))
189 : #define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1
190 : #include "../../util/tmpl/fd_map_chain.c"
191 :
192 : #define MAP_NAME fd_gui_peers_node_sock_map
193 0 : #define MAP_ELE_T fd_gui_peers_node_t
194 : #define MAP_KEY_T fd_ip4_port_t
195 0 : #define MAP_KEY contact_info.sockets[ FD_CONTACT_INFO_SOCKET_GOSSIP ]
196 0 : #define MAP_IDX_T ulong
197 0 : #define MAP_NEXT sock_map.next
198 0 : #define MAP_PREV sock_map.prev
199 0 : #define MAP_KEY_HASH(k,s) ( fd_hash( (s), (k), sizeof(uint) + sizeof(ushort) ) )
200 0 : #define MAP_KEY_EQ(k0,k1) ((k0)->l==(k1)->l )
201 : #define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1
202 : #define MAP_MULTI 1
203 : #include "../../util/tmpl/fd_map_chain.c"
204 :
205 0 : static int live_table_col_pubkey_lt( void const * a, void const * b ) { return memcmp( ((fd_pubkey_t *)a)->uc, ((fd_pubkey_t *)b)->uc, 32UL ) < 0; }
206 0 : static int live_table_col_long_lt ( void const * a, void const * b ) { return *(long *)a < *(long *)b; }
207 0 : static int live_table_col_ipv4_lt ( void const * a, void const * b ) { return fd_uint_bswap(*(uint *)a) < fd_uint_bswap(*(uint *)b); }
208 :
209 : #define LIVE_TABLE_NAME fd_gui_peers_live_table
210 0 : #define LIVE_TABLE_TREAP treaps_live_table
211 0 : #define LIVE_TABLE_SORT_KEYS sort_keys_live_table
212 0 : #define LIVE_TABLE_DLIST dlist_live_table
213 0 : #define LIVE_TABLE_COLUMN_CNT (6UL)
214 0 : #define LIVE_TABLE_MAX_SORT_KEY_CNT FD_GUI_PEERS_CI_TABLE_SORT_KEY_CNT
215 : #define LIVE_TABLE_ROW_T fd_gui_peers_node_t
216 0 : #define LIVE_TABLE_COLUMNS LIVE_TABLE_COL_ARRAY( \
217 0 : LIVE_TABLE_COL_ENTRY( "Ingress Push", gossvf_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PUSH_IDX ].rate, live_table_col_long_lt ), \
218 0 : LIVE_TABLE_COL_ENTRY( "Ingress Pull", gossvf_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PULL_RESPONSE_IDX ].rate, live_table_col_long_lt ), \
219 0 : LIVE_TABLE_COL_ENTRY( "Egress Push", gossip_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PUSH_IDX ].rate, live_table_col_long_lt ), \
220 0 : LIVE_TABLE_COL_ENTRY( "Egress Pull", gossip_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PULL_RESPONSE_IDX ].rate, live_table_col_long_lt ), \
221 0 : LIVE_TABLE_COL_ENTRY( "Pubkey", contact_info.pubkey, live_table_col_pubkey_lt ), \
222 0 : LIVE_TABLE_COL_ENTRY( "IP Addr", contact_info.sockets[ FD_CONTACT_INFO_SOCKET_GOSSIP ].addr, live_table_col_ipv4_lt ) )
223 : #include "fd_gui_live_table_tmpl.c"
224 :
225 0 : #define FD_GUI_PEERS_LIVE_TABLE_DEFAULT_SORT_KEY ((fd_gui_peers_live_table_sort_key_t){ .col = { 0, 1, 2, 3, 4, 5 }, .dir = { -1, -1, -1, -1, -1, -1 } })
226 :
227 : #define LIVE_TABLE_NAME fd_gui_peers_bandwidth_tracking
228 0 : #define LIVE_TABLE_TREAP treaps_bandwidth_tracking
229 0 : #define LIVE_TABLE_SORT_KEYS sort_keys_bandwidth_tracking
230 0 : #define LIVE_TABLE_DLIST dlist_bandwidth_tracking
231 0 : #define LIVE_TABLE_COLUMN_CNT (2UL)
232 0 : #define LIVE_TABLE_MAX_SORT_KEY_CNT (2UL)
233 : #define LIVE_TABLE_ROW_T fd_gui_peers_node_t
234 0 : #define LIVE_TABLE_COLUMNS LIVE_TABLE_COL_ARRAY( \
235 0 : LIVE_TABLE_COL_ENTRY( "Ingress Total", gossvf_rx_sum.rate, live_table_col_long_lt ), \
236 0 : LIVE_TABLE_COL_ENTRY( "Egress Total", gossip_tx_sum.rate, live_table_col_long_lt ) )
237 : #include "fd_gui_live_table_tmpl.c"
238 :
239 0 : #define FD_GUI_PEERS_BW_TRACKING_INGRESS_SORT_KEY ((fd_gui_peers_bandwidth_tracking_sort_key_t){ .col = { 0, 1 }, .dir = { -1, 0 } })
240 0 : #define FD_GUI_PEERS_BW_TRACKING_EGRESS_SORT_KEY ((fd_gui_peers_bandwidth_tracking_sort_key_t){ .col = { 0, 1 }, .dir = { 0, -1 } })
241 :
242 : struct fd_gui_peers_ws_conn {
243 : int connected;
244 : long connected_time;
245 :
246 : ulong start_row;
247 : ulong row_cnt;
248 : fd_gui_peers_node_t viewport[ FD_GUI_PEERS_WS_VIEWPORT_MAX_SZ ];
249 : fd_gui_peers_live_table_sort_key_t sort_key;
250 : };
251 : typedef struct fd_gui_peers_ws_conn fd_gui_peers_ws_conn_t;
252 : struct fd_gui_peers_ctx {
253 : long next_client_nanos; /* ns timestamp when we'll service the next ws client */
254 : long next_metric_rate_update_nanos; /* ns timestamp when we'll next update rate-of-change metrics */
255 : long next_gossip_stats_update_nanos; /* ns timestamp when we'll next broadcast out gossip stats message */
256 :
257 : fd_gui_peers_node_pubkey_map_t * node_pubkey_map;
258 : fd_gui_peers_node_sock_map_t * node_sock_map;
259 : fd_gui_peers_live_table_t * live_table;
260 : fd_gui_peers_bandwidth_tracking_t * bw_tracking;
261 :
262 : fd_http_server_t * http;
263 : fd_topo_t * topo;
264 :
265 : ulong max_ws_conn_cnt;
266 : ulong open_ws_conn_cnt;
267 : ulong active_ws_conn_id;
268 : fd_gui_peers_ws_conn_t * client_viewports; /* points to 2D array with max_ws_conn_cnt rows and FD_GUI_PEERS_WS_VIEWPORT_MAX_SZ columns */
269 :
270 : fd_gui_peers_gossip_stats_t gossip_stats [ 1 ];
271 : fd_gui_peers_node_t contact_info_table[ FD_CONTACT_INFO_TABLE_SIZE ];
272 :
273 : fd_gui_peers_vote_t votes [ FD_RUNTIME_MAX_VOTE_ACCOUNTS ];
274 : fd_gui_peers_vote_t votes_scratch[ FD_RUNTIME_MAX_VOTE_ACCOUNTS ]; /* for fast stable sort */
275 : };
276 : typedef struct fd_gui_peers_ctx fd_gui_peers_ctx_t;
277 :
278 : FD_PROTOTYPES_BEGIN
279 :
280 : FD_FN_CONST ulong
281 : fd_gui_peers_align( void );
282 :
283 : FD_FN_CONST ulong
284 : fd_gui_peers_footprint( ulong max_ws_conn_cnt );
285 :
286 : void *
287 : fd_gui_peers_new( void * shmem,
288 : fd_http_server_t * http,
289 : fd_topo_t * topo,
290 : ulong max_ws_conn_cnt,
291 : long now );
292 :
293 : fd_gui_peers_ctx_t *
294 : fd_gui_peers_join( void * shmem );
295 :
296 : /* fd_gui_peers_handle_gossip_message_rx parses gossip messages from the
297 : net_gossvf link for ingress messages and the gossip_net link for
298 : egress messages and tracks per-peer, per-message bytes. payload and
299 : payload_sz corresponds to the frag data after the network headers
300 : have been stripped. is_rx is true if the frag is an incoming message
301 : from the net_gossvf link. Otherwise, the frag is assumed to be an
302 : outgoing message from the gossip_net link. peer_sock is the ipv4
303 : address and port from the stripped net headers, which identifies the
304 : peers that sent or will receive the message.
305 :
306 : Note that gossip_net frags are unverified gossip messages from the
307 : network. Messages that cannot be parsed are ignored. */
308 : void
309 : fd_gui_peers_handle_gossip_message( fd_gui_peers_ctx_t * peers,
310 : uchar const * payload,
311 : ulong payload_sz,
312 : fd_ip4_port_t const * peer_sock,
313 : int is_rx );
314 :
315 : /* fd_gui_peers_handle_gossip_message_tx parses frags on the gossip_out
316 : link and uses the contact info update to build up the peer table.
317 : */
318 : void
319 : fd_gui_peers_handle_gossip_update( fd_gui_peers_ctx_t * peers,
320 : fd_gossip_update_message_t const * update,
321 : long now );
322 :
323 : void
324 : fd_gui_peers_handle_vote_update( fd_gui_peers_ctx_t * peers,
325 : fd_gui_peers_vote_t * votes,
326 : ulong vote_cnt,
327 : long now );
328 :
329 : /* fd_gui_peers_ws_message handles incoming websocket request payloads
330 : requesting peer-related responses. ws_conn_id is the connection id
331 : of the requester. data is a pointer to the start of the
332 : json-formatted request payload. data_len is the length of the
333 : request payload. */
334 : int
335 : fd_gui_peers_ws_message( fd_gui_peers_ctx_t * peers,
336 : ulong ws_conn_id,
337 : uchar const * data,
338 : ulong data_len );
339 :
340 : /* fd_gui_peers_ws_open is a callback which should be triggered when a
341 : new client opens a WebSocket connection. ws_conn_id is the
342 : connection id of the new client. now is a UNIX nanosecond timestamp
343 : for the current time. */
344 : void
345 : fd_gui_peers_ws_open( fd_gui_peers_ctx_t * peers, ulong ws_conn_id, long now );
346 :
347 : /* fd_gui_peers_ws_close is a callback which should be triggered when an
348 : existing client closes their WebSocket connection. ws_conn_id is the
349 : connection id of the client.*/
350 : void
351 : fd_gui_peers_ws_close( fd_gui_peers_ctx_t * peers, ulong ws_conn_id );
352 :
353 : /* fd_gui_peers_poll should be called in a the tile's main spin loop to
354 : periodically update peers internal state as well as publish new
355 : Websocket messages to clients. now is a UNIX nanosecond timestamp for
356 : the current time. */
357 : int
358 : fd_gui_peers_poll( fd_gui_peers_ctx_t * peers, long now );
359 :
360 : FD_PROTOTYPES_END
361 :
362 : #endif /* HEADER_fd_src_disco_gui_fd_gui_peers_h */
|