@cryptotaxi247 / netdata-1 / commits / 7da45658d

feat(topology): network-viewer and streaming topology functions (#22110)

* feat(topology): add network-viewer and streaming topology functions Add the streaming topology function that exposes the Netdata streaming infrastructure as a topology map with parent-child relationships and system metadata. Enhance the network-viewer plugin with process parent (ppid) tracking for accurate process tree grouping. * build: avoid i386 network-viewer LTO OOM * topology: fix review findings * topology: precompute streaming descendants * topology: fix follow-up streaming review issues * topology: split network viewer renderer * topology: align streaming and network viewer rows * topology: remove dead endpoint-as state * topology: tighten live topology contracts * topology: align streaming function contracts * topology: align streaming path since units * topology: harden streaming host identity handling * topology: fix streaming identity and IPv6 endpoint labels * topology: prefer machine guid over hostname ids * topology: align network-viewer cache metadata * topology: harden filters and process actors * topology: keep streaming ids canonical * topology: tighten network-viewer process ids * Fix string length issues, improve memory safety, and update chart labels in network viewer and streaming functions. --------- Co-authored-by: Stelios Fragkakis <52996999+stelfrag@users.noreply.github.com>

Costa Tsaousis committed Apr 14, 2026 at 11:51 UTC 7da45658d489cdb8b4514b20b4e4404492213ec5
10 files changed +4050 -156
CMakeLists.txt
+16
@@ -2993,12 +2993,28 @@ if(ENABLE_PLUGIN_LOCAL_LISTENERS)
2993 endif()
2994
2995 if(ENABLE_PLUGIN_NETWORK_VIEWER)
2996 + if(USE_LTO AND CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_SIZEOF_VOID_P EQUAL 4)
2997 + # GCC still performs LTO when linking slim LTO static archives into a
2998 + # non-LTO target. Build these archives as fat objects and force a
2999 + # regular final link to avoid exhausting the i386 linker.
3000 + if(TARGET judy)
3001 + target_compile_options(judy PRIVATE -ffat-lto-objects)
3002 + endif()
3003 + if(TARGET libnetdata)
3004 + target_compile_options(libnetdata PRIVATE -ffat-lto-objects)
3005 + endif()
3006 + endif()
3007 +
3008 set(NETWORK_VIEWER_FILES
3009 src/libnetdata/local-sockets/local-sockets.h
3010 src/collectors/network-viewer.plugin/network-viewer.c
3011 )
3012
3013 add_executable(network-viewer.plugin ${NETWORK_VIEWER_FILES})
3014 + if(USE_LTO AND CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_SIZEOF_VOID_P EQUAL 4)
3015 + set_property(TARGET network-viewer.plugin PROPERTY INTERPROCEDURAL_OPTIMIZATION FALSE)
3016 + target_link_options(network-viewer.plugin PRIVATE -fno-lto)
3017 + endif()
3018
3019 target_compile_options(network-viewer.plugin PRIVATE
3020 "$<$<BOOL:${MNL_FOUND}>:${MNL_CFLAGS_OTHER}>")
src/collectors/network-viewer.plugin/network-viewer.c
+2455 -144
@@ -28,6 +28,135 @@ static SPAWN_SERVER *spawn_srv = NULL;
28
29 #define NETWORK_CONNECTIONS_VIEWER_FUNCTION "network-connections"
30 #define NETWORK_CONNECTIONS_VIEWER_HELP "Shows active network connections with protocol details, states, addresses, ports, and performance metrics."
31 +#define NETWORK_TOPOLOGY_VIEWER_FUNCTION "topology:network-connections"
32 +#define NETWORK_TOPOLOGY_VIEWER_HELP "Shows live network-connections topology with self/process/endpoint actors and ownership/socket links."
33 +#define NETWORK_VIEWER_RESPONSE_UPDATE_EVERY 5
34 +// Keep in sync with the topology schema contract used across topology producers.
35 +#define NETWORK_TOPOLOGY_SCHEMA_VERSION "2.0"
36 +#define NETWORK_TOPOLOGY_SOURCE "network-connections"
37 +#define NETWORK_TOPOLOGY_LAYER "l7"
38 +#define NV_TOPOLOGY_MAX_PPID_DEPTH 64
39 +
40 +#define NV_TOPOLOGY_USERNAME_MAX 128
41 +#define NV_TOPOLOGY_CMDLINE_MAX 512
42 +#define NV_TOPOLOGY_KEY_MAX 1024
43 +
44 +typedef struct {
45 + pid_t pid;
46 + pid_t ppid;
47 + uid_t uid;
48 + uint64_t net_ns_inode;
49 + uint64_t sockets;
50 + char process[TASK_COMM_LEN + 1];
51 + char username[NV_TOPOLOGY_USERNAME_MAX];
52 + char namespace_type[16];
53 + char local_ip[INET6_ADDRSTRLEN];
54 + char local_address_space[16];
55 + char cmdline[NV_TOPOLOGY_CMDLINE_MAX];
56 +} NV_PROCESS_ACTOR;
57 +
58 +typedef struct {
59 + uint64_t sockets;
60 + char ip[INET6_ADDRSTRLEN];
61 + char address_space[16];
62 +} NV_REMOTE_ACTOR;
63 +
64 +typedef struct {
65 + uint64_t sockets;
66 + char ip[INET6_ADDRSTRLEN];
67 + char address_space[16];
68 +} NV_LOCAL_IP;
69 +
70 +typedef struct {
71 + uint64_t pid;
72 + uint64_t ppid;
73 + uint64_t uid;
74 + uint64_t net_ns_inode;
75 + char process[TASK_COMM_LEN + 1];
76 +} NV_ENDPOINT_OWNER;
77 +
78 +typedef struct {
79 + uint64_t pid;
80 + uint64_t ppid;
81 + uint64_t uid;
82 + uint64_t net_ns_inode;
83 + uint64_t sockets;
84 + uint64_t retransmissions;
85 + uint32_t max_rtt_usec;
86 + uint32_t max_rcv_rtt_usec;
87 + uint16_t local_port;
88 + uint16_t remote_port;
89 + uint16_t peer_port;
90 + uint16_t protocol_id;
91 + uint8_t direction_id;
92 + char process[TASK_COMM_LEN + 1];
93 + char username[NV_TOPOLOGY_USERNAME_MAX];
94 + char namespace_type[16];
95 + char protocol[8];
96 + char protocol_family[8];
97 + char direction[16];
98 + char state[32];
99 + char local_ip[INET6_ADDRSTRLEN];
100 + char remote_ip[INET6_ADDRSTRLEN];
101 + char peer_ip[INET6_ADDRSTRLEN];
102 + char local_address_space[16];
103 + char remote_address_space[16];
104 + char port_name[64];
105 + char cmdline[NV_TOPOLOGY_CMDLINE_MAX];
106 +} NV_TOPOLOGY_LINK;
107 +
108 +typedef struct {
109 + bool info_only;
110 + bool processes_by_pid;
111 + bool sockets_listening;
112 + bool sockets_local;
113 + bool sockets_inbound;
114 + bool sockets_outbound;
115 + bool protocols_ipv4_tcp;
116 + bool protocols_ipv6_tcp;
117 + bool protocols_ipv4_udp;
118 + bool protocols_ipv6_udp;
119 +} NV_TOPOLOGY_OPTIONS;
120 +
121 +typedef struct {
122 + DICTIONARY *process_actors;
123 + DICTIONARY *remote_actors;
124 + DICTIONARY *local_ips;
125 + DICTIONARY *endpoint_owners_exact;
126 + DICTIONARY *endpoint_owners_exact_any_ns;
127 + DICTIONARY *endpoint_owners_service;
128 + DICTIONARY *endpoint_owners_service_any_ns;
129 + DICTIONARY *links;
130 + usec_t now_ut;
131 + uint64_t sockets_total;
132 + uint64_t skipped_sockets;
133 + char hostname[256];
134 + char machine_guid[128];
135 + NV_TOPOLOGY_OPTIONS options;
136 +} NV_TOPOLOGY_CONTEXT;
137 +
138 +typedef struct {
139 + uint64_t ppid;
140 +} NV_PPID_CACHE_ENTRY;
141 +
142 +typedef struct {
143 + size_t process_actor_count;
144 + size_t socket_link_count;
145 + size_t local_ip_count;
146 + size_t endpoint_actor_count;
147 + size_t ownership_link_count;
148 + char host_actor_id[NV_TOPOLOGY_KEY_MAX];
149 +} NV_TOPOLOGY_RENDER_STATE;
150 +
151 +typedef struct nv_process_socket_row {
152 + const NV_TOPOLOGY_LINK *link;
153 + struct nv_process_socket_row *next;
154 +} NV_PROCESS_SOCKET_ROW;
155 +
156 +typedef struct {
157 + NV_PROCESS_SOCKET_ROW *head;
158 + NV_PROCESS_SOCKET_ROW *tail;
159 +} NV_PROCESS_SOCKET_ROWS;
160
161 #define SIMPLE_HASHTABLE_VALUE_TYPE LOCAL_SOCKET *
162 #define SIMPLE_HASHTABLE_NAME _AGGREGATED_SOCKETS
@@ -62,8 +191,8 @@ ENUM_STR_MAP_DEFINE(TCP_STATE) = {
191 { .id = TCP_ESTABLISHED, .name = "established" },
192 { .id = TCP_SYN_SENT, .name = "syn-sent" },
193 { .id = TCP_SYN_RECV, .name = "syn-received" },
65 - { .id = TCP_FIN_WAIT1, .name = "fin1-wait1" },
66 - { .id = TCP_FIN_WAIT2, .name = "fin1-wait2" },
194 + { .id = TCP_FIN_WAIT1, .name = "fin-wait1" },
195 + { .id = TCP_FIN_WAIT2, .name = "fin-wait2" },
196 { .id = TCP_TIME_WAIT, .name = "time-wait" },
197 { .id = TCP_CLOSE, .name = "close" },
198 { .id = TCP_CLOSE_WAIT, .name = "close-wait" },
@@ -86,6 +215,645 @@ struct sockets_stats {
215 } max;
216 };
217
218 +static inline const char *network_viewer_machine_guid(void) {
219 + const char *guid = getenv("NETDATA_REGISTRY_UNIQUE_ID");
220 + return (guid && *guid) ? guid : NULL;
221 +}
222 +
223 +static inline void topology_options_defaults(NV_TOPOLOGY_OPTIONS *opts) {
224 + if(!opts)
225 + return;
226 +
227 + memset(opts, 0, sizeof(*opts));
228 + opts->processes_by_pid = false; // default: by_name
229 + opts->sockets_listening = false;
230 + opts->sockets_local = false;
231 + opts->sockets_inbound = true;
232 + opts->sockets_outbound = true;
233 + opts->protocols_ipv4_tcp = true;
234 + opts->protocols_ipv6_tcp = true;
235 + opts->protocols_ipv4_udp = true;
236 + opts->protocols_ipv6_udp = true;
237 +}
238 +
239 +static inline bool topology_sockets_any_enabled(const NV_TOPOLOGY_OPTIONS *opts) {
240 + if(!opts)
241 + return false;
242 +
243 + return (opts->sockets_listening ||
244 + opts->sockets_local ||
245 + opts->sockets_inbound ||
246 + opts->sockets_outbound);
247 +}
248 +
249 +static inline bool topology_protocols_any_enabled(const NV_TOPOLOGY_OPTIONS *opts) {
250 + if(!opts)
251 + return false;
252 +
253 + return (opts->protocols_ipv4_tcp ||
254 + opts->protocols_ipv6_tcp ||
255 + opts->protocols_ipv4_udp ||
256 + opts->protocols_ipv6_udp);
257 +}
258 +
259 +static void topology_parse_options(const char *function, NV_TOPOLOGY_OPTIONS *opts) {
260 + topology_options_defaults(opts);
261 + if(!function || !*function || !opts)
262 + return;
263 +
264 + bool protocols_selected_explicitly = false;
265 + bool sockets_selected_explicitly = false;
266 +
267 + char *function_copy = strdupz(function);
268 + char *words[1024];
269 + size_t num_words = quoted_strings_splitter_whitespace(function_copy, words, 1024);
270 + for(size_t i = 1; i < num_words; i++) {
271 + char *param = get_word(words, num_words, i);
272 + if(!param || !*param)
273 + continue;
274 +
275 + if(strcmp(param, "info") == 0) {
276 + opts->info_only = true;
277 + continue;
278 + }
279 +
280 + if(strcmp(param, "processes:by_name") == 0 || strcmp(param, "processes:by-name") == 0) {
281 + opts->processes_by_pid = false;
282 + continue;
283 + }
284 + if(strcmp(param, "processes:by_pid") == 0 || strcmp(param, "processes:by-pid") == 0) {
285 + opts->processes_by_pid = true;
286 + continue;
287 + }
288 +
289 + if(strcmp(param, "endpoints:by_ip") == 0 || strcmp(param, "endpoints:by-ip") == 0)
290 + continue;
291 +
292 + if(strncmp(param, "sockets:", 8) == 0) {
293 + if(!sockets_selected_explicitly) {
294 + opts->sockets_listening = false;
295 + opts->sockets_local = false;
296 + opts->sockets_inbound = false;
297 + opts->sockets_outbound = false;
298 + sockets_selected_explicitly = true;
299 + }
300 +
301 + char *sockets_copy = strdupz(&param[8]);
302 + char *sockets_remaining = sockets_copy;
303 + char *socket_kind;
304 + while(sockets_remaining && *sockets_remaining &&
305 + (socket_kind = strsep_skip_consecutive_separators(&sockets_remaining, ","))) {
306 + socket_kind = trim(socket_kind);
307 + if(!socket_kind || !*socket_kind)
308 + continue;
309 +
310 + if(strcmp(socket_kind, "listening") == 0)
311 + opts->sockets_listening = true;
312 + else if(strcmp(socket_kind, "local") == 0)
313 + opts->sockets_local = true;
314 + else if(strcmp(socket_kind, "inbound") == 0)
315 + opts->sockets_inbound = true;
316 + else if(strcmp(socket_kind, "outbound") == 0)
317 + opts->sockets_outbound = true;
318 + }
319 + freez(sockets_copy);
320 + continue;
321 + }
322 +
323 + if(strncmp(param, "protocols:", 10) == 0) {
324 + if(!protocols_selected_explicitly) {
325 + opts->protocols_ipv4_tcp = false;
326 + opts->protocols_ipv6_tcp = false;
327 + opts->protocols_ipv4_udp = false;
328 + opts->protocols_ipv6_udp = false;
329 + protocols_selected_explicitly = true;
330 + }
331 +
332 + char *protocols_copy = strdupz(&param[10]);
333 + char *protocols_remaining = protocols_copy;
334 + char *protocol;
335 + while(protocols_remaining && *protocols_remaining &&
336 + (protocol = strsep_skip_consecutive_separators(&protocols_remaining, ","))) {
337 + protocol = trim(protocol);
338 + if(!protocol || !*protocol)
339 + continue;
340 +
341 + if(strcmp(protocol, "ipv4_tcp") == 0 || strcmp(protocol, "ipv4-tcp") == 0)
342 + opts->protocols_ipv4_tcp = true;
343 + else if(strcmp(protocol, "ipv6_tcp") == 0 || strcmp(protocol, "ipv6-tcp") == 0)
344 + opts->protocols_ipv6_tcp = true;
345 + else if(strcmp(protocol, "ipv4_udp") == 0 || strcmp(protocol, "ipv4-udp") == 0)
346 + opts->protocols_ipv4_udp = true;
347 + else if(strcmp(protocol, "ipv6_udp") == 0 || strcmp(protocol, "ipv6-udp") == 0)
348 + opts->protocols_ipv6_udp = true;
349 + }
350 + freez(protocols_copy);
351 + }
352 + }
353 + freez(function_copy);
354 +
355 + if(!topology_sockets_any_enabled(opts)) {
356 + opts->sockets_listening = false;
357 + opts->sockets_local = false;
358 + opts->sockets_inbound = true;
359 + opts->sockets_outbound = true;
360 + }
361 +
362 + if(!topology_protocols_any_enabled(opts)) {
363 + opts->protocols_ipv4_tcp = true;
364 + opts->protocols_ipv6_tcp = true;
365 + opts->protocols_ipv4_udp = true;
366 + opts->protocols_ipv6_udp = true;
367 + }
368 +}
369 +
370 +static inline const char *socket_protocol_name(uint16_t protocol) {
371 + return (protocol == IPPROTO_UDP) ? "udp" : "tcp";
372 +}
373 +
374 +static inline const char *socket_protocol_family_name(const LOCAL_SOCKET *n) {
375 + if(is_local_socket_ipv46(n))
376 + return "ipv46";
377 +
378 + if(n->local.family == AF_INET)
379 + return "ipv4";
380 +
381 + if(n->local.family == AF_INET6)
382 + return "ipv6";
383 +
384 + return "unknown";
385 +}
386 +
387 +static bool socket_endpoint_to_ip_text(const struct socket_endpoint *ep, char *dst) {
388 + if(ep->family == AF_INET) {
389 + ipv4_address_to_txt(ep->ip.ipv4, dst);
390 + return true;
391 + }
392 +
393 + if(ep->family == AF_INET6) {
394 + ipv6_address_to_txt(&ep->ip.ipv6, dst);
395 + return true;
396 + }
397 +
398 + dst[0] = '\0';
399 + return false;
400 +}
401 +
402 +static inline void topology_format_ip_port(const char *ip, uint16_t port, char *dst, size_t dst_size) {
403 + if(!dst || !dst_size)
404 + return;
405 +
406 + if(!ip)
407 + ip = "";
408 +
409 + if(strchr(ip, ':'))
410 + snprintf(dst, dst_size, "[%s]:%u", ip, port);
411 + else
412 + snprintf(dst, dst_size, "%s:%u", ip, port);
413 +}
414 +
415 +static inline bool topology_ip_is_unspecified(const char *ip) {
416 + if(!ip || !*ip)
417 + return true;
418 +
419 + return (strcmp(ip, "*") == 0 || strcmp(ip, "0.0.0.0") == 0 || strcmp(ip, "::") == 0);
420 +}
421 +
422 +static inline bool topology_ip_is_self_range(const char *ip) {
423 + if(!ip || !*ip)
424 + return false;
425 +
426 + if(strncmp(ip, "127.", 4) == 0)
427 + return true;
428 + if(strncmp(ip, "0.", 2) == 0)
429 + return true;
430 + if(strcmp(ip, "::1") == 0)
431 + return true;
432 + if(strncmp(ip, "::ffff:127.", 10) == 0)
433 + return true;
434 +
435 + return false;
436 +}
437 +
438 +static inline bool topology_ip_belongs_to_self(const NV_TOPOLOGY_CONTEXT *ctx, const char *ip, const char *address_space) {
439 + if(topology_ip_is_unspecified(ip))
440 + return true;
441 +
442 + if(topology_ip_is_self_range(ip))
443 + return true;
444 +
445 + if(address_space && *address_space) {
446 + if(strcmp(address_space, "loopback") == 0 || strcmp(address_space, "zero") == 0)
447 + return true;
448 + }
449 +
450 + if(ctx && ctx->local_ips && ip && *ip && dictionary_get(ctx->local_ips, ip))
451 + return true;
452 +
453 + return false;
454 +}
455 +
456 +static void topology_add_single_item_string_array(BUFFER *wb, const char *key, const char *value) {
457 + if(!value || !*value)
458 + return;
459 +
460 + if(strcmp(key, "ip_addresses") == 0 && strcmp(value, "*") == 0)
461 + return;
462 +
463 + buffer_json_member_add_array(wb, key);
464 + {
465 + buffer_json_add_array_item_string(wb, value);
466 + }
467 + buffer_json_array_close(wb);
468 +}
469 +
470 +static void topology_add_process_match(BUFFER *wb, const NV_TOPOLOGY_CONTEXT *ctx, const NV_PROCESS_ACTOR *pa) {
471 + buffer_json_member_add_object(wb, "match");
472 + {
473 + buffer_json_member_add_string(wb, "process_name", pa->process);
474 + if(ctx && ctx->options.processes_by_pid) {
475 + buffer_json_member_add_uint64(wb, "pid", pa->pid);
476 + buffer_json_member_add_uint64(wb, "uid", pa->uid);
477 + buffer_json_member_add_uint64(wb, "net_ns_inode", pa->net_ns_inode);
478 + }
479 + }
480 + buffer_json_object_close(wb);
481 +}
482 +
483 +static void topology_add_process_identity_match(BUFFER *wb, const NV_TOPOLOGY_CONTEXT *ctx, uint64_t pid, uint64_t uid, uint64_t net_ns_inode, const char *process_name) {
484 + buffer_json_member_add_object(wb, "match");
485 + {
486 + buffer_json_member_add_string(wb, "process_name", process_name && *process_name ? process_name : "[unknown]");
487 + if(ctx && ctx->options.processes_by_pid) {
488 + buffer_json_member_add_uint64(wb, "pid", pid);
489 + buffer_json_member_add_uint64(wb, "uid", uid);
490 + buffer_json_member_add_uint64(wb, "net_ns_inode", net_ns_inode);
491 + }
492 + }
493 + buffer_json_object_close(wb);
494 +}
495 +
496 +static inline void topology_process_parent_lookup_key(
497 + char *dst,
498 + size_t dst_size,
499 + uint64_t pid,
500 + uint64_t net_ns_inode,
501 + bool include_ns
502 +) {
503 + if(!dst || !dst_size)
504 + return;
505 +
506 + if(include_ns)
507 + snprintf(dst, dst_size, "ns=%llu|pid=%llu",
508 + (unsigned long long)net_ns_inode,
509 + (unsigned long long)pid);
510 + else
511 + snprintf(dst, dst_size, "pid=%llu",
512 + (unsigned long long)pid);
513 +}
514 +
515 +static inline void topology_pid_lookup_key(char *dst, size_t dst_size, uint64_t pid) {
516 + if(!dst || !dst_size)
517 + return;
518 +
519 + snprintf(dst, dst_size, "pid=%llu", (unsigned long long)pid);
520 +}
521 +
522 +static bool topology_read_proc_ppid(uint64_t pid, uint64_t *ppid) {
523 + if(!pid || !ppid)
524 + return false;
525 +
526 + char filename[FILENAME_MAX + 1];
527 + char status_buf[1024];
528 + snprintfz(filename, sizeof(filename), "%s/proc/%llu/status",
529 + netdata_configured_host_prefix,
530 + (unsigned long long)pid);
531 +
532 + if(read_txt_file(filename, status_buf, sizeof(status_buf)))
533 + return false;
534 +
535 + char *p = strstr(status_buf, "PPid:");
536 + if(!p)
537 + return false;
538 +
539 + p += 5;
540 + while(isspace((unsigned char)*p))
541 + p++;
542 +
543 + if(*p < '0' || *p > '9')
544 + return false;
545 +
546 + uint64_t parent = strtoull(p, NULL, 10);
547 + if(parent == pid)
548 + parent = 0;
549 +
550 + *ppid = parent;
551 + return true;
552 +}
553 +
554 +static uint64_t topology_ppid_cache_get_or_load(DICTIONARY *ppid_cache, uint64_t pid) {
555 + if(!ppid_cache || !pid)
556 + return 0;
557 +
558 + char pid_key[64];
559 + topology_pid_lookup_key(pid_key, sizeof(pid_key), pid);
560 +
561 + NV_PPID_CACHE_ENTRY *cached = dictionary_get(ppid_cache, pid_key);
562 + if(cached)
563 + return cached->ppid;
564 +
565 + NV_PPID_CACHE_ENTRY tmp = { .ppid = 0 };
566 + uint64_t ppid = 0;
567 + if(topology_read_proc_ppid(pid, &ppid))
568 + tmp.ppid = ppid;
569 +
570 + cached = dictionary_set(ppid_cache, pid_key, &tmp, sizeof(tmp));
571 + return cached ? cached->ppid : tmp.ppid;
572 +}
573 +
574 +static NV_ENDPOINT_OWNER *topology_find_process_parent_actor(
575 + uint64_t ppid,
576 + uint64_t net_ns_inode,
577 + DICTIONARY *process_parent_ns_lookup,
578 + DICTIONARY *process_parent_any_lookup,
579 + DICTIONARY *ppid_cache
580 +) {
581 + if(!ppid || !process_parent_ns_lookup || !process_parent_any_lookup)
582 + return NULL;
583 +
584 + uint64_t current_pid = ppid;
585 + for(size_t depth = 0; depth < NV_TOPOLOGY_MAX_PPID_DEPTH && current_pid; depth++) {
586 + char parent_key_ns[NV_TOPOLOGY_KEY_MAX];
587 + char parent_key_any[NV_TOPOLOGY_KEY_MAX];
588 +
589 + topology_process_parent_lookup_key(parent_key_ns, sizeof(parent_key_ns), current_pid, net_ns_inode, true);
590 + NV_ENDPOINT_OWNER *parent = dictionary_get(process_parent_ns_lookup, parent_key_ns);
591 + if(!parent) {
592 + topology_process_parent_lookup_key(parent_key_any, sizeof(parent_key_any), current_pid, 0, false);
593 + parent = dictionary_get(process_parent_any_lookup, parent_key_any);
594 + }
595 +
596 + if(parent)
597 + return parent;
598 +
599 + if(!ppid_cache)
600 + break;
601 +
602 + uint64_t next_ppid = topology_ppid_cache_get_or_load(ppid_cache, current_pid);
603 + if(!next_ppid || next_ppid == current_pid)
604 + break;
605 +
606 + current_pid = next_ppid;
607 + }
608 +
609 + return NULL;
610 +}
611 +
612 +static void topology_endpoint_owner_exact_key(char *dst, size_t dst_size, uint64_t net_ns_inode, uint16_t protocol, const char *ip, uint16_t port, bool include_ns) {
613 + if(!dst || !dst_size)
614 + return;
615 +
616 + if(include_ns)
617 + snprintf(dst, dst_size, "ns=%llu|proto=%u|ip=%s|port=%u",
618 + (unsigned long long)net_ns_inode, (unsigned)protocol, ip, (unsigned)port);
619 + else
620 + snprintf(dst, dst_size, "proto=%u|ip=%s|port=%u", (unsigned)protocol, ip, (unsigned)port);
621 +}
622 +
623 +static void topology_endpoint_owner_service_key(char *dst, size_t dst_size, uint64_t net_ns_inode, uint16_t protocol, uint16_t port, bool include_ns) {
624 + if(!dst || !dst_size)
625 + return;
626 +
627 + if(include_ns)
628 + snprintf(dst, dst_size, "ns=%llu|proto=%u|port=%u",
629 + (unsigned long long)net_ns_inode, (unsigned)protocol, (unsigned)port);
630 + else
631 + snprintf(dst, dst_size, "proto=%u|port=%u", (unsigned)protocol, (unsigned)port);
632 +}
633 +
634 +static void topology_register_endpoint_owner(
635 + NV_TOPOLOGY_CONTEXT *ctx,
636 + uint64_t net_ns_inode,
637 + uint16_t protocol,
638 + const char *ip,
639 + uint16_t port,
640 + const NV_PROCESS_ACTOR *pa,
641 + bool service_candidate
642 +) {
643 + if(!ctx || !pa || !port)
644 + return;
645 +
646 + NV_ENDPOINT_OWNER owner = {
647 + .pid = pa->pid,
648 + .ppid = pa->ppid,
649 + .uid = pa->uid,
650 + .net_ns_inode = pa->net_ns_inode,
651 + };
652 + snprintf(owner.process, sizeof(owner.process), "%s", pa->process);
653 +
654 + char key[NV_TOPOLOGY_KEY_MAX];
655 + if(ip && *ip && strcmp(ip, "*") != 0) {
656 + if(ctx->endpoint_owners_exact) {
657 + topology_endpoint_owner_exact_key(key, sizeof(key), net_ns_inode, protocol, ip, port, true);
658 + dictionary_set(ctx->endpoint_owners_exact, key, &owner, sizeof(owner));
659 + }
660 + if(ctx->endpoint_owners_exact_any_ns) {
661 + topology_endpoint_owner_exact_key(key, sizeof(key), 0, protocol, ip, port, false);
662 + dictionary_set(ctx->endpoint_owners_exact_any_ns, key, &owner, sizeof(owner));
663 + }
664 + }
665 +
666 + if(service_candidate) {
667 + if(ctx->endpoint_owners_service) {
668 + topology_endpoint_owner_service_key(key, sizeof(key), net_ns_inode, protocol, port, true);
669 + dictionary_set(ctx->endpoint_owners_service, key, &owner, sizeof(owner));
670 + }
671 + if(ctx->endpoint_owners_service_any_ns) {
672 + topology_endpoint_owner_service_key(key, sizeof(key), 0, protocol, port, false);
673 + dictionary_set(ctx->endpoint_owners_service_any_ns, key, &owner, sizeof(owner));
674 + }
675 + }
676 +}
677 +
678 +static NV_ENDPOINT_OWNER *topology_lookup_endpoint_owner(
679 + const NV_TOPOLOGY_CONTEXT *ctx,
680 + uint64_t net_ns_inode,
681 + uint16_t protocol,
682 + const char *ip,
683 + uint16_t port,
684 + bool allow_service_fallback
685 +) {
686 + if(!ctx || !port)
687 + return NULL;
688 +
689 + char key[NV_TOPOLOGY_KEY_MAX];
690 + NV_ENDPOINT_OWNER *owner = NULL;
691 +
692 + if(ip && *ip && strcmp(ip, "*") != 0) {
693 + if(ctx->endpoint_owners_exact) {
694 + topology_endpoint_owner_exact_key(key, sizeof(key), net_ns_inode, protocol, ip, port, true);
695 + owner = dictionary_get(ctx->endpoint_owners_exact, key);
696 + if(owner)
697 + return owner;
698 + }
699 +
700 + if(ctx->endpoint_owners_exact_any_ns) {
701 + topology_endpoint_owner_exact_key(key, sizeof(key), 0, protocol, ip, port, false);
702 + owner = dictionary_get(ctx->endpoint_owners_exact_any_ns, key);
703 + if(owner)
704 + return owner;
705 + }
706 + }
707 +
708 + if(!allow_service_fallback)
709 + return NULL;
710 +
711 + if(ctx->endpoint_owners_service) {
712 + topology_endpoint_owner_service_key(key, sizeof(key), net_ns_inode, protocol, port, true);
713 + owner = dictionary_get(ctx->endpoint_owners_service, key);
714 + if(owner)
715 + return owner;
716 + }
717 +
718 + if(ctx->endpoint_owners_service_any_ns) {
719 + topology_endpoint_owner_service_key(key, sizeof(key), 0, protocol, port, false);
720 + owner = dictionary_get(ctx->endpoint_owners_service_any_ns, key);
721 + if(owner)
722 + return owner;
723 + }
724 +
725 + return NULL;
726 +}
727 +
728 +static void topology_add_host_match(BUFFER *wb, const NV_TOPOLOGY_CONTEXT *ctx) {
729 + buffer_json_member_add_object(wb, "match");
730 + {
731 + if(ctx->machine_guid[0])
732 + buffer_json_member_add_string(wb, "netdata_machine_guid", ctx->machine_guid);
733 +
734 + topology_add_single_item_string_array(wb, "hostnames", ctx->hostname);
735 + buffer_json_member_add_array(wb, "ip_addresses");
736 + {
737 + NV_LOCAL_IP *lip;
738 + dfe_start_read(ctx->local_ips, lip) {
739 + if(!lip->ip[0]) continue;
740 + if(topology_ip_is_unspecified(lip->ip)) continue;
741 + buffer_json_add_array_item_string(wb, lip->ip);
742 + }
743 + dfe_done(lip);
744 + }
745 + buffer_json_array_close(wb);
746 + }
747 + buffer_json_object_close(wb);
748 +}
749 +
750 +static void topology_add_remote_match(BUFFER *wb, const char *ip) {
751 + buffer_json_member_add_object(wb, "match");
752 + {
753 + topology_add_single_item_string_array(wb, "ip_addresses", ip);
754 + }
755 + buffer_json_object_close(wb);
756 +}
757 +
758 +static void topology_encode_identifier_component(char *dst, size_t dst_size, const char *src) {
759 + if(!dst || !dst_size)
760 + return;
761 +
762 + const uint8_t *s = (const uint8_t *)((src && *src) ? src : "[unknown]");
763 + size_t written = 0;
764 +
765 + while(*s && written + 1 < dst_size) {
766 + uint8_t ch = *s++;
767 +
768 + if(isalnum(ch) || ch == '-' || ch == '_' || ch == '.' || ch == '~') {
769 + dst[written++] = (char)ch;
770 + continue;
771 + }
772 +
773 + if(written + 3 >= dst_size)
774 + break;
775 +
776 + dst[written++] = '%';
777 + dst[written++] = hex_digits_lower[(ch >> 4) & 0x0F];
778 + dst[written++] = hex_digits_lower[ch & 0x0F];
779 + }
780 +
781 + dst[written] = '\0';
782 +}
783 +
784 +static void topology_actor_id_for_host(const NV_TOPOLOGY_CONTEXT *ctx, char *dst, size_t dst_size) {
785 + if(!dst || !dst_size)
786 + return;
787 +
788 + if(ctx && ctx->machine_guid[0])
789 + snprintf(dst, dst_size, "netdata-machine-guid:%s", ctx->machine_guid);
790 + else if(ctx && ctx->hostname[0])
791 + snprintf(dst, dst_size, "hostname:%s", ctx->hostname);
792 + else
793 + snprintf(dst, dst_size, "host:unknown");
794 +}
795 +
796 +static void topology_actor_id_for_process(
797 + const NV_TOPOLOGY_CONTEXT *ctx,
798 + uint64_t pid,
799 + uint64_t uid,
800 + uint64_t net_ns_inode,
801 + const char *process,
802 + char *dst,
803 + size_t dst_size
804 +) {
805 + if(!dst || !dst_size)
806 + return;
807 +
808 + const char *node_identity = (ctx && ctx->machine_guid[0]) ? ctx->machine_guid : (ctx && ctx->hostname[0] ? ctx->hostname : "unknown");
809 + const char *safe_process = (process && *process) ? process : "[unknown]";
810 + if(ctx && !ctx->options.processes_by_pid) {
811 + char encoded_process[((TASK_COMM_LEN + 1) * 3) + 1];
812 + topology_encode_identifier_component(encoded_process, sizeof(encoded_process), safe_process);
813 + snprintf(dst, dst_size, "process:%s|comm=%s", node_identity, encoded_process);
814 + }
815 + else {
816 + snprintf(dst, dst_size, "process:%s|pid=%llu|uid=%llu|ns=%llu",
817 + node_identity,
818 + (unsigned long long)pid,
819 + (unsigned long long)uid,
820 + (unsigned long long)net_ns_inode);
821 + }
822 +}
823 +
824 +static bool topology_process_name_is_unknown(const char *process_name) {
825 + return !process_name || !*process_name || strcmp(process_name, "[unknown]") == 0;
826 +}
827 +
828 +static void topology_actor_id_for_remote_endpoint(const NV_TOPOLOGY_CONTEXT *ctx, const char *ip, const char *address_space, char *dst, size_t dst_size) {
829 + if(!dst || !dst_size)
830 + return;
831 +
832 + (void)ctx;
833 + (void)address_space;
834 + if(ip && *ip)
835 + snprintf(dst, dst_size, "ip:%s", ip);
836 + else
837 + snprintf(dst, dst_size, "ip:unknown");
838 +}
839 +
840 +static void topology_process_display_name(
841 + const NV_TOPOLOGY_CONTEXT *ctx,
842 + const char *process_name,
843 + uint64_t pid,
844 + char *dst,
845 + size_t dst_size
846 +) {
847 + if(!dst || !dst_size)
848 + return;
849 +
850 + const char *safe_process = (process_name && *process_name) ? process_name : "[unknown]";
851 + if(ctx && !ctx->options.processes_by_pid)
852 + snprintf(dst, dst_size, "%s", safe_process);
853 + else
854 + snprintf(dst, dst_size, "%s[%llu]", safe_process, (unsigned long long)pid);
855 +}
856 +
857 static void local_socket_to_json_array(struct sockets_stats *st, const LOCAL_SOCKET *n, uint64_t proc_self_net_ns_inode, bool aggregated) {
858 if(n->direction == SOCKET_DIRECTION_NONE)
859 return;
@@ -241,169 +1009,1693 @@ static void local_socket_to_json_array(struct sockets_stats *st, const LOCAL_SOC
1009 if(st->max.tcpi_total_retrans < n->info.tcp.tcpi_total_retrans)
1010 st->max.tcpi_total_retrans = n->info.tcp.tcpi_total_retrans;
1011
244 - // count
245 - buffer_json_add_array_item_uint64(wb, n->network_viewer.count);
1012 + // count
1013 + buffer_json_add_array_item_uint64(wb, n->network_viewer.count);
1014 + }
1015 + buffer_json_array_close(wb);
1016 +}
1017 +
1018 +static void populate_aggregated_key(const LOCAL_SOCKET *nn) {
1019 + LOCAL_SOCKET *n = (LOCAL_SOCKET *)nn;
1020 +
1021 + n->network_viewer.count = 1;
1022 +
1023 + n->network_viewer.aggregated_key.pid = n->pid;
1024 + n->network_viewer.aggregated_key.uid = n->uid;
1025 + n->network_viewer.aggregated_key.direction = n->direction;
1026 + n->network_viewer.aggregated_key.net_ns_inode = n->net_ns_inode;
1027 + n->network_viewer.aggregated_key.state = n->state;
1028 +
1029 + switch(n->direction) {
1030 + case SOCKET_DIRECTION_INBOUND:
1031 + case SOCKET_DIRECTION_LOCAL_INBOUND:
1032 + case SOCKET_DIRECTION_LISTEN:
1033 + n->network_viewer.aggregated_key.server = n->local;
1034 + break;
1035 +
1036 + case SOCKET_DIRECTION_OUTBOUND:
1037 + case SOCKET_DIRECTION_LOCAL_OUTBOUND:
1038 + n->network_viewer.aggregated_key.server = n->remote;
1039 + break;
1040 +
1041 + case SOCKET_DIRECTION_NONE:
1042 + break;
1043 + }
1044 +
1045 + n->network_viewer.aggregated_key.local_address_space = local_sockets_address_space(&n->local);
1046 + n->network_viewer.aggregated_key.remote_address_space = local_sockets_address_space(&n->remote);
1047 +}
1048 +
1049 +static void local_sockets_cb_to_json(LS_STATE *ls, const LOCAL_SOCKET *n, void *data) {
1050 + struct sockets_stats *st = data;
1051 + populate_aggregated_key(n);
1052 + local_socket_to_json_array(st, n, ls->proc_self_net_ns_inode, false);
1053 +}
1054 +
1055 +#define KEEP_THE_BIGGER(a, b) (a) = ((a) < (b)) ? (b) : (a)
1056 +#define KEEP_THE_SMALLER(a, b) (a) = ((a) > (b)) ? (b) : (a)
1057 +#define SUM_THEM_ALL(a, b) (a) += (b)
1058 +#define OR_THEM_ALL(a, b) (a) |= (b)
1059 +
1060 +static void local_sockets_cb_to_aggregation(LS_STATE *ls __maybe_unused, const LOCAL_SOCKET *n, void *data) {
1061 + SIMPLE_HASHTABLE_AGGREGATED_SOCKETS *ht = data;
1062 +
1063 + populate_aggregated_key(n);
1064 + XXH64_hash_t hash = XXH3_64bits(&n->network_viewer.aggregated_key, sizeof(n->network_viewer.aggregated_key));
1065 + SIMPLE_HASHTABLE_SLOT_AGGREGATED_SOCKETS *sl = simple_hashtable_get_slot_AGGREGATED_SOCKETS(ht, hash, (LOCAL_SOCKET *)n, true);
1066 + LOCAL_SOCKET *t = SIMPLE_HASHTABLE_SLOT_DATA(sl);
1067 + if(t) {
1068 + t->network_viewer.count++;
1069 +
1070 + KEEP_THE_BIGGER(t->timer, n->timer);
1071 + KEEP_THE_BIGGER(t->retransmits, n->retransmits);
1072 + KEEP_THE_SMALLER(t->expires, n->expires);
1073 + KEEP_THE_BIGGER(t->rqueue, n->rqueue);
1074 + KEEP_THE_BIGGER(t->wqueue, n->wqueue);
1075 +
1076 + // The current number of consecutive retransmissions that have occurred for the most recently transmitted segment.
1077 + SUM_THEM_ALL(t->info.tcp.tcpi_retransmits, n->info.tcp.tcpi_retransmits);
1078 +
1079 + // The total number of retransmissions that have occurred for the entire connection since it was established.
1080 + SUM_THEM_ALL(t->info.tcp.tcpi_total_retrans, n->info.tcp.tcpi_total_retrans);
1081 +
1082 + // The total number of segments that have been retransmitted since the connection was established.
1083 + SUM_THEM_ALL(t->info.tcp.tcpi_retrans, n->info.tcp.tcpi_retrans);
1084 +
1085 + // The number of keepalive probes sent
1086 + SUM_THEM_ALL(t->info.tcp.tcpi_probes, n->info.tcp.tcpi_probes);
1087 +
1088 + // The number of times the retransmission timeout has been backed off.
1089 + SUM_THEM_ALL(t->info.tcp.tcpi_backoff, n->info.tcp.tcpi_backoff);
1090 +
1091 + // A bitmask representing the TCP options currently enabled for the connection, such as SACK and Timestamps.
1092 + OR_THEM_ALL(t->info.tcp.tcpi_options, n->info.tcp.tcpi_options);
1093 +
1094 + // The send window scale value used for this connection
1095 + KEEP_THE_SMALLER(t->info.tcp.tcpi_snd_wscale, n->info.tcp.tcpi_snd_wscale);
1096 +
1097 + // The receive window scale value used for this connection
1098 + KEEP_THE_SMALLER(t->info.tcp.tcpi_rcv_wscale, n->info.tcp.tcpi_rcv_wscale);
1099 +
1100 + // Retransmission timeout in milliseconds
1101 + KEEP_THE_SMALLER(t->info.tcp.tcpi_rto, n->info.tcp.tcpi_rto);
1102 +
1103 + // The delayed acknowledgement timeout in milliseconds.
1104 + KEEP_THE_SMALLER(t->info.tcp.tcpi_ato, n->info.tcp.tcpi_ato);
1105 +
1106 + // The maximum segment size for sending.
1107 + KEEP_THE_SMALLER(t->info.tcp.tcpi_snd_mss, n->info.tcp.tcpi_snd_mss);
1108 +
1109 + // The maximum segment size for receiving.
1110 + KEEP_THE_SMALLER(t->info.tcp.tcpi_rcv_mss, n->info.tcp.tcpi_rcv_mss);
1111 +
1112 + // The number of unacknowledged segments
1113 + SUM_THEM_ALL(t->info.tcp.tcpi_unacked, n->info.tcp.tcpi_unacked);
1114 +
1115 + // The number of segments that have been selectively acknowledged
1116 + SUM_THEM_ALL(t->info.tcp.tcpi_sacked, n->info.tcp.tcpi_sacked);
1117 +
1118 + // The number of lost segments.
1119 + SUM_THEM_ALL(t->info.tcp.tcpi_lost, n->info.tcp.tcpi_lost);
1120 +
1121 + // The number of forward acknowledgment segments.
1122 + SUM_THEM_ALL(t->info.tcp.tcpi_fackets, n->info.tcp.tcpi_fackets);
1123 +
1124 + // The time in milliseconds since the last data was sent.
1125 + KEEP_THE_SMALLER(t->info.tcp.tcpi_last_data_sent, n->info.tcp.tcpi_last_data_sent);
1126 +
1127 + // The time in milliseconds since the last acknowledgment was sent (not tracked in Linux, hence often zero).
1128 + KEEP_THE_SMALLER(t->info.tcp.tcpi_last_ack_sent, n->info.tcp.tcpi_last_ack_sent);
1129 +
1130 + // The time in milliseconds since the last data was received.
1131 + KEEP_THE_SMALLER(t->info.tcp.tcpi_last_data_recv, n->info.tcp.tcpi_last_data_recv);
1132 +
1133 + // The time in milliseconds since the last acknowledgment was received.
1134 + KEEP_THE_SMALLER(t->info.tcp.tcpi_last_ack_recv, n->info.tcp.tcpi_last_ack_recv);
1135 +
1136 + // The path MTU for this connection
1137 + KEEP_THE_SMALLER(t->info.tcp.tcpi_pmtu, n->info.tcp.tcpi_pmtu);
1138 +
1139 + // The slow start threshold for receiving
1140 + KEEP_THE_SMALLER(t->info.tcp.tcpi_rcv_ssthresh, n->info.tcp.tcpi_rcv_ssthresh);
1141 +
1142 + // The slow start threshold for sending
1143 + KEEP_THE_SMALLER(t->info.tcp.tcpi_snd_ssthresh, n->info.tcp.tcpi_snd_ssthresh);
1144 +
1145 + // The round trip time in milliseconds
1146 + KEEP_THE_BIGGER(t->info.tcp.tcpi_rtt, n->info.tcp.tcpi_rtt);
1147 +
1148 + // The round trip time variance in milliseconds.
1149 + KEEP_THE_BIGGER(t->info.tcp.tcpi_rttvar, n->info.tcp.tcpi_rttvar);
1150 +
1151 + // The size of the sending congestion window.
1152 + KEEP_THE_SMALLER(t->info.tcp.tcpi_snd_cwnd, n->info.tcp.tcpi_snd_cwnd);
1153 +
1154 + // The maximum segment size that could be advertised.
1155 + KEEP_THE_BIGGER(t->info.tcp.tcpi_advmss, n->info.tcp.tcpi_advmss);
1156 +
1157 + // The reordering metric
1158 + KEEP_THE_SMALLER(t->info.tcp.tcpi_reordering, n->info.tcp.tcpi_reordering);
1159 +
1160 + // The receive round trip time in milliseconds.
1161 + KEEP_THE_BIGGER(t->info.tcp.tcpi_rcv_rtt, n->info.tcp.tcpi_rcv_rtt);
1162 +
1163 + // The available space in the receive buffer.
1164 + KEEP_THE_SMALLER(t->info.tcp.tcpi_rcv_space, n->info.tcp.tcpi_rcv_space);
1165 + }
1166 + else {
1167 + t = mallocz(sizeof(*t));
1168 + memcpy(t, n, sizeof(*t));
1169 + t->cmdline = string_dup(t->cmdline);
1170 + simple_hashtable_set_slot_AGGREGATED_SOCKETS(ht, sl, hash, t);
1171 + }
1172 +}
1173 +
1174 +static void local_sockets_cb_to_topology(LS_STATE *ls, const LOCAL_SOCKET *n, void *data) {
1175 + if(n->direction == SOCKET_DIRECTION_NONE)
1176 + return;
1177 +
1178 + NV_TOPOLOGY_CONTEXT *ctx = data;
1179 + ctx->sockets_total++;
1180 +
1181 + char local_ip[INET6_ADDRSTRLEN] = "";
1182 + char remote_ip[INET6_ADDRSTRLEN] = "";
1183 + char remote_peer_ip[INET6_ADDRSTRLEN] = "";
1184 +
1185 + if(is_local_socket_ipv46(n))
1186 + strncpyz(local_ip, "*", sizeof(local_ip) - 1);
1187 + else if(!socket_endpoint_to_ip_text(&n->local, local_ip))
1188 + return;
1189 +
1190 + if(!local_sockets_is_zero_address(&n->remote)) {
1191 + socket_endpoint_to_ip_text(&n->remote, remote_ip);
1192 + snprintf(remote_peer_ip, sizeof(remote_peer_ip), "%s", remote_ip);
1193 + }
1194 +
1195 + const char *namespace_type;
1196 + if(n->net_ns_inode == ls->proc_self_net_ns_inode)
1197 + namespace_type = "system";
1198 + else if(n->net_ns_inode == 0)
1199 + namespace_type = "unknown";
1200 + else
1201 + namespace_type = "container";
1202 +
1203 + const char *local_address_space = local_sockets_address_space(&n->local);
1204 + const char *remote_address_space = local_sockets_address_space(&n->remote);
1205 + const char *process_name = n->comm[0] ? n->comm : "[unknown]";
1206 + const char *cmdline = string2str(n->cmdline);
1207 +
1208 + char username[NV_TOPOLOGY_USERNAME_MAX] = "[unknown]";
1209 + if(n->uid != UID_UNSET) {
1210 + CACHED_USERNAME cu = cached_username_get_by_uid(n->uid);
1211 + const char *cached_username = string2str(cu.username);
1212 + if(cached_username && *cached_username)
1213 + snprintf(username, sizeof(username), "%s", cached_username);
1214 + cached_username_release(cu);
1215 + }
1216 +
1217 + if(local_ip[0] && !topology_ip_is_unspecified(local_ip)) {
1218 + NV_LOCAL_IP *local_actor = dictionary_get(ctx->local_ips, local_ip);
1219 + if(!local_actor) {
1220 + NV_LOCAL_IP tmp = { 0 };
1221 + snprintf(tmp.ip, sizeof(tmp.ip), "%s", local_ip);
1222 + snprintf(tmp.address_space, sizeof(tmp.address_space), "%s", local_address_space);
1223 + local_actor = dictionary_set(ctx->local_ips, local_ip, &tmp, sizeof(tmp));
1224 + }
1225 + local_actor->sockets++;
1226 + }
1227 +
1228 + char process_key[NV_TOPOLOGY_KEY_MAX];
1229 + if(ctx->options.processes_by_pid) {
1230 + snprintf(process_key, sizeof(process_key), "pid=%d|uid=%u|ns=%llu",
1231 + n->pid,
1232 + (unsigned)n->uid,
1233 + (unsigned long long)n->net_ns_inode);
1234 + }
1235 + else {
1236 + char encoded_process[((TASK_COMM_LEN + 1) * 3) + 1];
1237 + topology_encode_identifier_component(encoded_process, sizeof(encoded_process), process_name);
1238 + snprintf(process_key, sizeof(process_key), "comm=%s", encoded_process);
1239 + }
1240 +
1241 + NV_PROCESS_ACTOR *pa = dictionary_get(ctx->process_actors, process_key);
1242 + if(!pa) {
1243 + NV_PROCESS_ACTOR tmp = { 0 };
1244 + tmp.pid = n->pid;
1245 + tmp.ppid = n->ppid;
1246 + tmp.uid = n->uid;
1247 + tmp.net_ns_inode = n->net_ns_inode;
1248 + snprintf(tmp.process, sizeof(tmp.process), "%s", process_name);
1249 + snprintf(tmp.username, sizeof(tmp.username), "%s", username);
1250 + snprintf(tmp.namespace_type, sizeof(tmp.namespace_type), "%s", namespace_type);
1251 + snprintf(tmp.local_ip, sizeof(tmp.local_ip), "%s", local_ip);
1252 + snprintf(tmp.local_address_space, sizeof(tmp.local_address_space), "%s", local_address_space);
1253 + if(cmdline && *cmdline)
1254 + snprintf(tmp.cmdline, sizeof(tmp.cmdline), "%s", cmdline);
1255 + pa = dictionary_set(ctx->process_actors, process_key, &tmp, sizeof(tmp));
1256 + }
1257 + pa->sockets++;
1258 + if(!pa->ppid && n->ppid)
1259 + pa->ppid = n->ppid;
1260 + if(topology_process_name_is_unknown(pa->process) && !topology_process_name_is_unknown(process_name))
1261 + snprintf(pa->process, sizeof(pa->process), "%s", process_name);
1262 + if(!pa->cmdline[0] && cmdline && *cmdline)
1263 + snprintf(pa->cmdline, sizeof(pa->cmdline), "%s", cmdline);
1264 + if((!pa->local_ip[0] || topology_ip_is_unspecified(pa->local_ip)) && local_ip[0] && !topology_ip_is_unspecified(local_ip))
1265 + snprintf(pa->local_ip, sizeof(pa->local_ip), "%s", local_ip);
1266 +
1267 + bool service_candidate = (n->direction == SOCKET_DIRECTION_LISTEN ||
1268 + n->direction == SOCKET_DIRECTION_INBOUND ||
1269 + n->direction == SOCKET_DIRECTION_LOCAL_INBOUND);
1270 + topology_register_endpoint_owner(ctx, n->net_ns_inode, n->local.protocol, local_ip, n->local.port, pa, service_candidate);
1271 +
1272 + if(!remote_ip[0]) {
1273 + if(n->direction == SOCKET_DIRECTION_LISTEN || n->direction == SOCKET_DIRECTION_LOCAL_INBOUND) {
1274 + if(strcmp(local_ip, "*") == 0 || local_sockets_is_zero_address(&n->local)) {
1275 + ctx->skipped_sockets++;
1276 + return;
1277 + }
1278 + snprintf(remote_ip, sizeof(remote_ip), "%s", local_ip);
1279 + remote_address_space = local_address_space;
1280 + }
1281 + else {
1282 + ctx->skipped_sockets++;
1283 + return;
1284 + }
1285 + }
1286 +
1287 + if(topology_ip_is_unspecified(remote_ip)) {
1288 + ctx->skipped_sockets++;
1289 + return;
1290 + }
1291 +
1292 + bool remote_is_self = topology_ip_belongs_to_self(ctx, remote_ip, remote_address_space);
1293 + bool create_endpoint_actor = (!remote_is_self || n->direction == SOCKET_DIRECTION_LISTEN);
1294 + if(create_endpoint_actor) {
1295 + char endpoint_actor_key[NV_TOPOLOGY_KEY_MAX];
1296 + topology_actor_id_for_remote_endpoint(ctx, remote_ip, remote_address_space, endpoint_actor_key, sizeof(endpoint_actor_key));
1297 + NV_REMOTE_ACTOR *ra = dictionary_get(ctx->remote_actors, endpoint_actor_key);
1298 + if(!ra) {
1299 + NV_REMOTE_ACTOR tmp = { 0 };
1300 + snprintf(tmp.ip, sizeof(tmp.ip), "%s", remote_ip);
1301 + snprintf(tmp.address_space, sizeof(tmp.address_space), "%s", remote_address_space);
1302 + ra = dictionary_set(ctx->remote_actors, endpoint_actor_key, &tmp, sizeof(tmp));
1303 + }
1304 + ra->sockets++;
1305 + }
1306 +
1307 + const struct socket_endpoint *server_endpoint = NULL;
1308 + uint16_t endpoint_port = n->remote.port;
1309 + switch(n->direction) {
1310 + case SOCKET_DIRECTION_LISTEN:
1311 + case SOCKET_DIRECTION_INBOUND:
1312 + case SOCKET_DIRECTION_LOCAL_INBOUND:
1313 + server_endpoint = &n->local;
1314 + endpoint_port = n->local.port;
1315 + break;
1316 +
1317 + case SOCKET_DIRECTION_OUTBOUND:
1318 + case SOCKET_DIRECTION_LOCAL_OUTBOUND:
1319 + server_endpoint = &n->remote;
1320 + endpoint_port = n->remote.port;
1321 + break;
1322 +
1323 + default:
1324 + break;
1325 + }
1326 +
1327 + char port_name[64] = "[unknown]";
1328 + if(server_endpoint) {
1329 + STRING *serv = system_servicenames_cache_lookup(sc, server_endpoint->port, server_endpoint->protocol);
1330 + const char *tmp_name = string2str(serv);
1331 + if(tmp_name && *tmp_name)
1332 + snprintf(port_name, sizeof(port_name), "%s", tmp_name);
1333 + }
1334 +
1335 + char link_key[NV_TOPOLOGY_KEY_MAX];
1336 + snprintf(link_key, sizeof(link_key), "pid=%d|uid=%u|ns=%llu|local=%s|remote=%s|proto=%u|dir=%u|state=%u|lport=%u|rport=%u",
1337 + n->pid,
1338 + (unsigned)n->uid,
1339 + (unsigned long long)n->net_ns_inode,
1340 + local_ip,
1341 + remote_ip,
1342 + (unsigned)n->local.protocol,
1343 + (unsigned)n->direction,
1344 + (unsigned)n->state,
1345 + n->local.port,
1346 + endpoint_port);
1347 +
1348 + NV_TOPOLOGY_LINK *link = dictionary_get(ctx->links, link_key);
1349 + if(!link) {
1350 + NV_TOPOLOGY_LINK tmp = { 0 };
1351 + tmp.pid = n->pid;
1352 + tmp.ppid = n->ppid;
1353 + tmp.uid = n->uid;
1354 + tmp.net_ns_inode = n->net_ns_inode;
1355 + tmp.local_port = n->local.port;
1356 + tmp.remote_port = endpoint_port;
1357 + tmp.peer_port = n->remote.port;
1358 + tmp.protocol_id = n->local.protocol;
1359 + tmp.direction_id = (uint8_t)n->direction;
1360 + snprintf(tmp.process, sizeof(tmp.process), "%s", process_name);
1361 + snprintf(tmp.username, sizeof(tmp.username), "%s", username);
1362 + snprintf(tmp.namespace_type, sizeof(tmp.namespace_type), "%s", namespace_type);
1363 + snprintf(tmp.protocol, sizeof(tmp.protocol), "%s", socket_protocol_name(n->local.protocol));
1364 + snprintf(tmp.protocol_family, sizeof(tmp.protocol_family), "%s", socket_protocol_family_name(n));
1365 + snprintf(tmp.direction, sizeof(tmp.direction), "%s", SOCKET_DIRECTION_2str(n->direction));
1366 + snprintf(tmp.state, sizeof(tmp.state), "%s",
1367 + n->local.protocol == IPPROTO_TCP ? TCP_STATE_2str(n->state) : "stateless");
1368 + snprintf(tmp.local_ip, sizeof(tmp.local_ip), "%s", local_ip);
1369 + snprintf(tmp.remote_ip, sizeof(tmp.remote_ip), "%s", remote_ip);
1370 + snprintf(tmp.peer_ip, sizeof(tmp.peer_ip), "%s", remote_peer_ip);
1371 + snprintf(tmp.local_address_space, sizeof(tmp.local_address_space), "%s", local_address_space);
1372 + snprintf(tmp.remote_address_space, sizeof(tmp.remote_address_space), "%s", remote_address_space);
1373 + snprintf(tmp.port_name, sizeof(tmp.port_name), "%s", port_name);
1374 + if(cmdline && *cmdline)
1375 + snprintf(tmp.cmdline, sizeof(tmp.cmdline), "%s", cmdline);
1376 + link = dictionary_set(ctx->links, link_key, &tmp, sizeof(tmp));
1377 + }
1378 +
1379 + link->sockets++;
1380 + link->retransmissions += n->info.tcp.tcpi_total_retrans;
1381 + if(link->max_rtt_usec < n->info.tcp.tcpi_rtt)
1382 + link->max_rtt_usec = n->info.tcp.tcpi_rtt;
1383 + if(link->max_rcv_rtt_usec < n->info.tcp.tcpi_rcv_rtt)
1384 + link->max_rcv_rtt_usec = n->info.tcp.tcpi_rcv_rtt;
1385 +}
1386 +
1387 +static void topology_context_destroy(NV_TOPOLOGY_CONTEXT *ctx) {
1388 + if(!ctx)
1389 + return;
1390 +
1391 + if(ctx->links)
1392 + dictionary_destroy(ctx->links);
1393 + if(ctx->endpoint_owners_service_any_ns)
1394 + dictionary_destroy(ctx->endpoint_owners_service_any_ns);
1395 + if(ctx->endpoint_owners_service)
1396 + dictionary_destroy(ctx->endpoint_owners_service);
1397 + if(ctx->endpoint_owners_exact_any_ns)
1398 + dictionary_destroy(ctx->endpoint_owners_exact_any_ns);
1399 + if(ctx->endpoint_owners_exact)
1400 + dictionary_destroy(ctx->endpoint_owners_exact);
1401 + if(ctx->local_ips)
1402 + dictionary_destroy(ctx->local_ips);
1403 + if(ctx->remote_actors)
1404 + dictionary_destroy(ctx->remote_actors);
1405 + if(ctx->process_actors)
1406 + dictionary_destroy(ctx->process_actors);
1407 +}
1408 +
1409 +static DICTIONARY *topology_build_process_socket_index(const NV_TOPOLOGY_CONTEXT *ctx) {
1410 + if(!ctx || !ctx->links)
1411 + return NULL;
1412 +
1413 + DICTIONARY *index = dictionary_create_advanced(
1414 + DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
1415 + NULL,
1416 + sizeof(NV_PROCESS_SOCKET_ROWS));
1417 + if(!index)
1418 + return NULL;
1419 +
1420 + NV_TOPOLOGY_LINK *link;
1421 + dfe_start_read(ctx->links, link) {
1422 + char process_actor_id[NV_TOPOLOGY_KEY_MAX];
1423 + topology_actor_id_for_process(ctx, link->pid, link->uid, link->net_ns_inode, link->process, process_actor_id, sizeof(process_actor_id));
1424 +
1425 + NV_PROCESS_SOCKET_ROWS *rows = dictionary_get(index, process_actor_id);
1426 + if(!rows) {
1427 + NV_PROCESS_SOCKET_ROWS tmp = { 0 };
1428 + rows = dictionary_set(index, process_actor_id, &tmp, sizeof(tmp));
1429 + }
1430 +
1431 + if(!rows)
1432 + continue;
1433 +
1434 + NV_PROCESS_SOCKET_ROW *row = callocz(1, sizeof(*row));
1435 + row->link = link;
1436 +
1437 + if(rows->tail)
1438 + rows->tail->next = row;
1439 + else
1440 + rows->head = row;
1441 + rows->tail = row;
1442 + }
1443 + dfe_done(link);
1444 +
1445 + return index;
1446 +}
1447 +
1448 +static void topology_destroy_process_socket_index(DICTIONARY *index) {
1449 + if(!index)
1450 + return;
1451 +
1452 + NV_PROCESS_SOCKET_ROWS *rows;
1453 + dfe_start_read(index, rows) {
1454 + NV_PROCESS_SOCKET_ROW *row = rows->head;
1455 + while(row) {
1456 + NV_PROCESS_SOCKET_ROW *next = row->next;
1457 + freez(row);
1458 + row = next;
1459 + }
1460 + }
1461 + dfe_done(rows);
1462 +
1463 + dictionary_destroy(index);
1464 +}
1465 +
1466 +static bool topology_prepare_context(NV_TOPOLOGY_CONTEXT *ctx, usec_t now_ut, const NV_TOPOLOGY_OPTIONS *options) {
1467 + if(!ctx)
1468 + return false;
1469 +
1470 + memset(ctx, 0, sizeof(*ctx));
1471 + ctx->now_ut = now_ut;
1472 + if(options)
1473 + ctx->options = *options;
1474 +
1475 + if(ctx->options.info_only)
1476 + return true;
1477 +
1478 + ctx->process_actors = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, NULL, sizeof(NV_PROCESS_ACTOR));
1479 + ctx->remote_actors = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, NULL, sizeof(NV_REMOTE_ACTOR));
1480 + ctx->local_ips = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, NULL, sizeof(NV_LOCAL_IP));
1481 + ctx->endpoint_owners_exact = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, NULL, sizeof(NV_ENDPOINT_OWNER));
1482 + ctx->endpoint_owners_exact_any_ns = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, NULL, sizeof(NV_ENDPOINT_OWNER));
1483 + ctx->endpoint_owners_service = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, NULL, sizeof(NV_ENDPOINT_OWNER));
1484 + ctx->endpoint_owners_service_any_ns = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, NULL, sizeof(NV_ENDPOINT_OWNER));
1485 + ctx->links = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, NULL, sizeof(NV_TOPOLOGY_LINK));
1486 +
1487 + if(!(ctx->process_actors && ctx->remote_actors && ctx->local_ips &&
1488 + ctx->endpoint_owners_exact && ctx->endpoint_owners_exact_any_ns &&
1489 + ctx->endpoint_owners_service && ctx->endpoint_owners_service_any_ns &&
1490 + ctx->links))
1491 + return false;
1492 +
1493 + if(!os_hostname(ctx->hostname, sizeof(ctx->hostname), netdata_configured_host_prefix))
1494 + snprintf(ctx->hostname, sizeof(ctx->hostname), "%s", "localhost");
1495 +
1496 + const char *machine_guid = network_viewer_machine_guid();
1497 + if(machine_guid)
1498 + snprintf(ctx->machine_guid, sizeof(ctx->machine_guid), "%s", machine_guid);
1499 +
1500 + LS_STATE ls = {
1501 + .config = {
1502 + .listening = ctx->options.sockets_listening,
1503 + .local = ctx->options.sockets_local,
1504 + .inbound = ctx->options.sockets_inbound,
1505 + .outbound = ctx->options.sockets_outbound,
1506 + .tcp4 = ctx->options.protocols_ipv4_tcp,
1507 + .tcp6 = ctx->options.protocols_ipv6_tcp,
1508 + .udp4 = ctx->options.protocols_ipv4_udp,
1509 + .udp6 = ctx->options.protocols_ipv6_udp,
1510 + .pid = true,
1511 + .uid = true,
1512 + .cmdline = true,
1513 + .comm = true,
1514 + .namespaces = true,
1515 + .tcp_info = true,
1516 + .max_errors = 10,
1517 + .max_concurrent_namespaces = 5,
1518 + .cb = local_sockets_cb_to_topology,
1519 + .data = ctx,
1520 + },
1521 +#if defined(LOCAL_SOCKETS_USE_SETNS)
1522 + .spawn_server = spawn_srv,
1523 +#endif
1524 + .stats = { 0 },
1525 + .sockets_hashtable = { 0 },
1526 + .local_ips_hashtable = { 0 },
1527 + .listening_ports_hashtable = { 0 },
1528 + };
1529 +
1530 + local_sockets_process(&ls);
1531 + return true;
1532 +}
1533 +
1534 +static void topology_render_state_init(NV_TOPOLOGY_RENDER_STATE *state, const NV_TOPOLOGY_CONTEXT *ctx) {
1535 + if(!state)
1536 + return;
1537 +
1538 + memset(state, 0, sizeof(*state));
1539 + if(!ctx)
1540 + return;
1541 +
1542 + state->process_actor_count = ctx->process_actors ? dictionary_entries(ctx->process_actors) : 0;
1543 + state->socket_link_count = ctx->links ? dictionary_entries(ctx->links) : 0;
1544 + state->local_ip_count = ctx->local_ips ? dictionary_entries(ctx->local_ips) : 0;
1545 + topology_actor_id_for_host(ctx, state->host_actor_id, sizeof(state->host_actor_id));
1546 +}
1547 +
1548 +static void topology_finalize_response(const char *transaction, BUFFER *wb, time_t now_s) {
1549 + buffer_json_member_add_time_t(wb, "expires", now_s + NETWORK_VIEWER_RESPONSE_UPDATE_EVERY);
1550 + buffer_json_finalize(wb);
1551 +
1552 + netdata_mutex_lock(&stdout_mutex);
1553 + wb->response_code = HTTP_RESP_OK;
1554 + wb->content_type = CT_APPLICATION_JSON;
1555 + wb->expires = now_s + NETWORK_VIEWER_RESPONSE_UPDATE_EVERY;
1556 + pluginsd_function_result_to_stdout(transaction, wb);
1557 + netdata_mutex_unlock(&stdout_mutex);
1558 +}
1559 +
1560 +static void topology_write_response_metadata(BUFFER *wb) {
1561 + buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK);
1562 + buffer_json_member_add_string(wb, "type", "topology");
1563 + buffer_json_member_add_time_t(wb, "update_every", NETWORK_VIEWER_RESPONSE_UPDATE_EVERY);
1564 + buffer_json_member_add_boolean(wb, "has_history", false);
1565 + buffer_json_member_add_string(wb, "help", NETWORK_TOPOLOGY_VIEWER_HELP);
1566 + buffer_json_member_add_array(wb, "accepted_params");
1567 + {
1568 + buffer_json_add_array_item_string(wb, "info");
1569 + buffer_json_add_array_item_string(wb, "processes");
1570 + buffer_json_add_array_item_string(wb, "sockets");
1571 + buffer_json_add_array_item_string(wb, "protocols");
1572 + buffer_json_add_array_item_string(wb, "endpoints");
1573 + }
1574 + buffer_json_array_close(wb);
1575 + buffer_json_member_add_array(wb, "required_params");
1576 + {
1577 + buffer_json_add_array_item_object(wb);
1578 + {
1579 + buffer_json_member_add_string(wb, "id", "processes");
1580 + buffer_json_member_add_string(wb, "name", "Processes");
1581 + buffer_json_member_add_string(wb, "help", "Group process actors by process name or by PID.");
1582 + buffer_json_member_add_boolean(wb, "unique_view", true);
1583 + buffer_json_member_add_string(wb, "type", "select");
1584 + buffer_json_member_add_array(wb, "options");
1585 + {
1586 + buffer_json_add_array_item_object(wb);
1587 + {
1588 + buffer_json_member_add_string(wb, "id", "by_name");
1589 + buffer_json_member_add_string(wb, "name", "Processes by Name");
1590 + buffer_json_member_add_boolean(wb, "defaultSelected", true);
1591 + }
1592 + buffer_json_object_close(wb);
1593 + buffer_json_add_array_item_object(wb);
1594 + {
1595 + buffer_json_member_add_string(wb, "id", "by_pid");
1596 + buffer_json_member_add_string(wb, "name", "Processes by PID");
1597 + }
1598 + buffer_json_object_close(wb);
1599 + }
1600 + buffer_json_array_close(wb);
1601 + }
1602 + buffer_json_object_close(wb);
1603 +
1604 + buffer_json_add_array_item_object(wb);
1605 + {
1606 + buffer_json_member_add_string(wb, "id", "sockets");
1607 + buffer_json_member_add_string(wb, "name", "Sockets");
1608 + buffer_json_member_add_string(wb, "help", "Select one or more socket directions.");
1609 + buffer_json_member_add_string(wb, "type", "multiselect");
1610 + buffer_json_member_add_array(wb, "options");
1611 + {
1612 + buffer_json_add_array_item_object(wb);
1613 + {
1614 + buffer_json_member_add_string(wb, "id", "listening");
1615 + buffer_json_member_add_string(wb, "name", "Listening");
1616 + }
1617 + buffer_json_object_close(wb);
1618 + buffer_json_add_array_item_object(wb);
1619 + {
1620 + buffer_json_member_add_string(wb, "id", "local");
1621 + buffer_json_member_add_string(wb, "name", "Local");
1622 + }
1623 + buffer_json_object_close(wb);
1624 + buffer_json_add_array_item_object(wb);
1625 + {
1626 + buffer_json_member_add_string(wb, "id", "inbound");
1627 + buffer_json_member_add_string(wb, "name", "Inbound");
1628 + buffer_json_member_add_boolean(wb, "defaultSelected", true);
1629 + }
1630 + buffer_json_object_close(wb);
1631 + buffer_json_add_array_item_object(wb);
1632 + {
1633 + buffer_json_member_add_string(wb, "id", "outbound");
1634 + buffer_json_member_add_string(wb, "name", "Outbound");
1635 + buffer_json_member_add_boolean(wb, "defaultSelected", true);
1636 + }
1637 + buffer_json_object_close(wb);
1638 + }
1639 + buffer_json_array_close(wb);
1640 + }
1641 + buffer_json_object_close(wb);
1642 +
1643 + buffer_json_add_array_item_object(wb);
1644 + {
1645 + buffer_json_member_add_string(wb, "id", "protocols");
1646 + buffer_json_member_add_string(wb, "name", "Protocols");
1647 + buffer_json_member_add_string(wb, "help", "Select one or more socket protocol families.");
1648 + buffer_json_member_add_string(wb, "type", "multiselect");
1649 + buffer_json_member_add_array(wb, "options");
1650 + {
1651 + buffer_json_add_array_item_object(wb);
1652 + {
1653 + buffer_json_member_add_string(wb, "id", "ipv4_tcp");
1654 + buffer_json_member_add_string(wb, "name", "IPv4 TCP");
1655 + buffer_json_member_add_boolean(wb, "defaultSelected", true);
1656 + }
1657 + buffer_json_object_close(wb);
1658 + buffer_json_add_array_item_object(wb);
1659 + {
1660 + buffer_json_member_add_string(wb, "id", "ipv6_tcp");
1661 + buffer_json_member_add_string(wb, "name", "IPv6 TCP");
1662 + buffer_json_member_add_boolean(wb, "defaultSelected", true);
1663 + }
1664 + buffer_json_object_close(wb);
1665 + buffer_json_add_array_item_object(wb);
1666 + {
1667 + buffer_json_member_add_string(wb, "id", "ipv4_udp");
1668 + buffer_json_member_add_string(wb, "name", "IPv4 UDP");
1669 + buffer_json_member_add_boolean(wb, "defaultSelected", true);
1670 + }
1671 + buffer_json_object_close(wb);
1672 + buffer_json_add_array_item_object(wb);
1673 + {
1674 + buffer_json_member_add_string(wb, "id", "ipv6_udp");
1675 + buffer_json_member_add_string(wb, "name", "IPv6 UDP");
1676 + buffer_json_member_add_boolean(wb, "defaultSelected", true);
1677 + }
1678 + buffer_json_object_close(wb);
1679 + }
1680 + buffer_json_array_close(wb);
1681 + }
1682 + buffer_json_object_close(wb);
1683 +
1684 + buffer_json_add_array_item_object(wb);
1685 + {
1686 + buffer_json_member_add_string(wb, "id", "endpoints");
1687 + buffer_json_member_add_string(wb, "name", "Endpoints");
1688 + buffer_json_member_add_string(wb, "help", "Keep non-private endpoints by IP until AS grouping is available.");
1689 + buffer_json_member_add_boolean(wb, "unique_view", true);
1690 + buffer_json_member_add_string(wb, "type", "select");
1691 + buffer_json_member_add_array(wb, "options");
1692 + {
1693 + buffer_json_add_array_item_object(wb);
1694 + {
1695 + buffer_json_member_add_string(wb, "id", "by_ip");
1696 + buffer_json_member_add_string(wb, "name", "Non-Private Endpoints by IP");
1697 + buffer_json_member_add_boolean(wb, "defaultSelected", true);
1698 + }
1699 + buffer_json_object_close(wb);
1700 + }
1701 + buffer_json_array_close(wb);
1702 + }
1703 + buffer_json_object_close(wb);
1704 + }
1705 + buffer_json_array_close(wb);
1706 +}
1707 +
1708 +static void topology_write_presentation(BUFFER *wb) {
1709 + buffer_json_member_add_object(wb, "presentation");
1710 + {
1711 + buffer_json_member_add_object(wb, "actor_types");
1712 + {
1713 + buffer_json_member_add_object(wb, "self");
1714 + {
1715 + buffer_json_member_add_string(wb, "label", "This host");
1716 + buffer_json_member_add_string(wb, "color_slot", "self");
1717 + buffer_json_member_add_boolean(wb, "border", true);
1718 + buffer_json_member_add_boolean(wb, "size_by_links", true);
1719 +
1720 + buffer_json_member_add_array(wb, "summary_fields");
1721 + {
1722 + buffer_json_add_array_item_object(wb);
1723 + buffer_json_member_add_string(wb, "key", "hostname");
1724 + buffer_json_member_add_string(wb, "label", "Hostname");
1725 + buffer_json_member_add_array(wb, "sources");
1726 + buffer_json_add_array_item_string(wb, "attributes.hostname");
1727 + buffer_json_array_close(wb);
1728 + buffer_json_object_close(wb);
1729 +
1730 + buffer_json_add_array_item_object(wb);
1731 + buffer_json_member_add_string(wb, "key", "local_ip_count");
1732 + buffer_json_member_add_string(wb, "label", "Local IPs");
1733 + buffer_json_member_add_array(wb, "sources");
1734 + buffer_json_add_array_item_string(wb, "attributes.local_ip_count");
1735 + buffer_json_array_close(wb);
1736 + buffer_json_object_close(wb);
1737 +
1738 + buffer_json_add_array_item_object(wb);
1739 + buffer_json_member_add_string(wb, "key", "observed_sockets");
1740 + buffer_json_member_add_string(wb, "label", "Sockets");
1741 + buffer_json_member_add_array(wb, "sources");
1742 + buffer_json_add_array_item_string(wb, "attributes.observed_sockets");
1743 + buffer_json_array_close(wb);
1744 + buffer_json_object_close(wb);
1745 + }
1746 + buffer_json_array_close(wb);
1747 +
1748 + buffer_json_member_add_object(wb, "tables");
1749 + {
1750 + buffer_json_member_add_object(wb, "links");
1751 + {
1752 + buffer_json_member_add_string(wb, "label", "Connections");
1753 + buffer_json_member_add_string(wb, "source", "links");
1754 + buffer_json_member_add_array(wb, "columns");
1755 + {
1756 + buffer_json_add_array_item_object(wb);
1757 + buffer_json_member_add_string(wb, "key", "remoteLabel");
1758 + buffer_json_member_add_string(wb, "label", "Remote");
1759 + buffer_json_member_add_string(wb, "type", "actor_link");
1760 + buffer_json_object_close(wb);
1761 +
1762 + buffer_json_add_array_item_object(wb);
1763 + buffer_json_member_add_string(wb, "key", "protocol");
1764 + buffer_json_member_add_string(wb, "label", "Protocol");
1765 + buffer_json_object_close(wb);
1766 +
1767 + buffer_json_add_array_item_object(wb);
1768 + buffer_json_member_add_string(wb, "key", "direction");
1769 + buffer_json_member_add_string(wb, "label", "Direction");
1770 + buffer_json_object_close(wb);
1771 + }
1772 + buffer_json_array_close(wb);
1773 + }
1774 + buffer_json_object_close(wb);
1775 + }
1776 + buffer_json_object_close(wb);
1777 +
1778 + buffer_json_member_add_array(wb, "modal_tabs");
1779 + {
1780 + buffer_json_add_array_item_object(wb);
1781 + buffer_json_member_add_string(wb, "id", "info");
1782 + buffer_json_member_add_string(wb, "label", "Info");
1783 + buffer_json_object_close(wb);
1784 + }
1785 + buffer_json_array_close(wb);
1786 + }
1787 + buffer_json_object_close(wb);
1788 +
1789 + buffer_json_member_add_object(wb, "process");
1790 + {
1791 + buffer_json_member_add_string(wb, "label", "Process");
1792 + buffer_json_member_add_string(wb, "color_slot", "primary");
1793 + buffer_json_member_add_boolean(wb, "border", true);
1794 + buffer_json_member_add_boolean(wb, "size_by_links", true);
1795 + buffer_json_member_add_boolean(wb, "show_port_bullets", true);
1796 +
1797 + buffer_json_member_add_array(wb, "summary_fields");
1798 + {
1799 + buffer_json_add_array_item_object(wb);
1800 + buffer_json_member_add_string(wb, "key", "display_name");
1801 + buffer_json_member_add_string(wb, "label", "Process");
1802 + buffer_json_member_add_array(wb, "sources");
1803 + buffer_json_add_array_item_string(wb, "attributes.display_name");
1804 + buffer_json_array_close(wb);
1805 + buffer_json_object_close(wb);
1806 +
1807 + buffer_json_add_array_item_object(wb);
1808 + buffer_json_member_add_string(wb, "key", "cmdline");
1809 + buffer_json_member_add_string(wb, "label", "Command");
1810 + buffer_json_member_add_array(wb, "sources");
1811 + buffer_json_add_array_item_string(wb, "attributes.cmdline");
1812 + buffer_json_array_close(wb);
1813 + buffer_json_object_close(wb);
1814 +
1815 + buffer_json_add_array_item_object(wb);
1816 + buffer_json_member_add_string(wb, "key", "socket_count");
1817 + buffer_json_member_add_string(wb, "label", "Sockets");
1818 + buffer_json_member_add_array(wb, "sources");
1819 + buffer_json_add_array_item_string(wb, "attributes.socket_count");
1820 + buffer_json_array_close(wb);
1821 + buffer_json_object_close(wb);
1822 +
1823 + buffer_json_add_array_item_object(wb);
1824 + buffer_json_member_add_string(wb, "key", "local_ip");
1825 + buffer_json_member_add_string(wb, "label", "Local IP");
1826 + buffer_json_member_add_array(wb, "sources");
1827 + buffer_json_add_array_item_string(wb, "attributes.local_ip");
1828 + buffer_json_array_close(wb);
1829 + buffer_json_object_close(wb);
1830 +
1831 + buffer_json_add_array_item_object(wb);
1832 + buffer_json_member_add_string(wb, "key", "user");
1833 + buffer_json_member_add_string(wb, "label", "User");
1834 + buffer_json_member_add_array(wb, "sources");
1835 + buffer_json_add_array_item_string(wb, "labels.user");
1836 + buffer_json_array_close(wb);
1837 + buffer_json_object_close(wb);
1838 + }
1839 + buffer_json_array_close(wb);
1840 +
1841 + buffer_json_member_add_object(wb, "tables");
1842 + {
1843 + buffer_json_member_add_object(wb, "sockets");
1844 + {
1845 + buffer_json_member_add_string(wb, "label", "Sockets");
1846 + buffer_json_member_add_string(wb, "source", "data");
1847 + buffer_json_member_add_boolean(wb, "bullet_source", true);
1848 + buffer_json_member_add_uint64(wb, "order", 0);
1849 + buffer_json_member_add_array(wb, "columns");
1850 + {
1851 + buffer_json_add_array_item_object(wb);
1852 + buffer_json_member_add_string(wb, "key", "remote");
1853 + buffer_json_member_add_string(wb, "label", "Remote");
1854 + buffer_json_object_close(wb);
1855 +
1856 + buffer_json_add_array_item_object(wb);
1857 + buffer_json_member_add_string(wb, "key", "protocol");
1858 + buffer_json_member_add_string(wb, "label", "Protocol");
1859 + buffer_json_member_add_string(wb, "type", "badge");
1860 + buffer_json_object_close(wb);
1861 +
1862 + buffer_json_add_array_item_object(wb);
1863 + buffer_json_member_add_string(wb, "key", "direction");
1864 + buffer_json_member_add_string(wb, "label", "Direction");
1865 + buffer_json_member_add_string(wb, "type", "badge");
1866 + buffer_json_object_close(wb);
1867 +
1868 + buffer_json_add_array_item_object(wb);
1869 + buffer_json_member_add_string(wb, "key", "state");
1870 + buffer_json_member_add_string(wb, "label", "State");
1871 + buffer_json_member_add_string(wb, "type", "badge");
1872 + buffer_json_object_close(wb);
1873 + }
1874 + buffer_json_array_close(wb);
1875 + }
1876 + buffer_json_object_close(wb);
1877 +
1878 + buffer_json_member_add_object(wb, "links");
1879 + {
1880 + buffer_json_member_add_string(wb, "label", "Connections");
1881 + buffer_json_member_add_string(wb, "source", "links");
1882 + buffer_json_member_add_uint64(wb, "order", 1);
1883 + buffer_json_member_add_array(wb, "columns");
1884 + {
1885 + buffer_json_add_array_item_object(wb);
1886 + buffer_json_member_add_string(wb, "key", "remoteLabel");
1887 + buffer_json_member_add_string(wb, "label", "Remote");
1888 + buffer_json_member_add_string(wb, "type", "actor_link");
1889 + buffer_json_object_close(wb);
1890 +
1891 + buffer_json_add_array_item_object(wb);
1892 + buffer_json_member_add_string(wb, "key", "protocol");
1893 + buffer_json_member_add_string(wb, "label", "Protocol");
1894 + buffer_json_object_close(wb);
1895 +
1896 + buffer_json_add_array_item_object(wb);
1897 + buffer_json_member_add_string(wb, "key", "direction");
1898 + buffer_json_member_add_string(wb, "label", "Direction");
1899 + buffer_json_object_close(wb);
1900 +
1901 + buffer_json_add_array_item_object(wb);
1902 + buffer_json_member_add_string(wb, "key", "state");
1903 + buffer_json_member_add_string(wb, "label", "State");
1904 + buffer_json_object_close(wb);
1905 + }
1906 + buffer_json_array_close(wb);
1907 + }
1908 + buffer_json_object_close(wb);
1909 + }
1910 + buffer_json_object_close(wb);
1911 +
1912 + buffer_json_member_add_array(wb, "modal_tabs");
1913 + {
1914 + buffer_json_add_array_item_object(wb);
1915 + buffer_json_member_add_string(wb, "id", "info");
1916 + buffer_json_member_add_string(wb, "label", "Info");
1917 + buffer_json_object_close(wb);
1918 + }
1919 + buffer_json_array_close(wb);
1920 + }
1921 + buffer_json_object_close(wb);
1922 +
1923 + buffer_json_member_add_object(wb, "endpoint");
1924 + {
1925 + buffer_json_member_add_string(wb, "label", "Endpoint");
1926 + buffer_json_member_add_string(wb, "color_slot", "derived");
1927 + buffer_json_member_add_boolean(wb, "border", true);
1928 +
1929 + buffer_json_member_add_array(wb, "summary_fields");
1930 + {
1931 + buffer_json_add_array_item_object(wb);
1932 + buffer_json_member_add_string(wb, "key", "display_name");
1933 + buffer_json_member_add_string(wb, "label", "IP Address");
1934 + buffer_json_member_add_array(wb, "sources");
1935 + buffer_json_add_array_item_string(wb, "attributes.display_name");
1936 + buffer_json_add_array_item_string(wb, "match.ip_addresses.0");
1937 + buffer_json_array_close(wb);
1938 + buffer_json_object_close(wb);
1939 +
1940 + buffer_json_add_array_item_object(wb);
1941 + buffer_json_member_add_string(wb, "key", "socket_count");
1942 + buffer_json_member_add_string(wb, "label", "Sockets");
1943 + buffer_json_member_add_array(wb, "sources");
1944 + buffer_json_add_array_item_string(wb, "attributes.socket_count");
1945 + buffer_json_array_close(wb);
1946 + buffer_json_object_close(wb);
1947 +
1948 + buffer_json_add_array_item_object(wb);
1949 + buffer_json_member_add_string(wb, "key", "address_space");
1950 + buffer_json_member_add_string(wb, "label", "Address Space");
1951 + buffer_json_member_add_array(wb, "sources");
1952 + buffer_json_add_array_item_string(wb, "labels.address_space");
1953 + buffer_json_array_close(wb);
1954 + buffer_json_object_close(wb);
1955 + }
1956 + buffer_json_array_close(wb);
1957 +
1958 + buffer_json_member_add_object(wb, "tables");
1959 + {
1960 + buffer_json_member_add_object(wb, "links");
1961 + {
1962 + buffer_json_member_add_string(wb, "label", "Connections");
1963 + buffer_json_member_add_string(wb, "source", "links");
1964 + buffer_json_member_add_array(wb, "columns");
1965 + {
1966 + buffer_json_add_array_item_object(wb);
1967 + buffer_json_member_add_string(wb, "key", "remoteLabel");
1968 + buffer_json_member_add_string(wb, "label", "Remote");
1969 + buffer_json_member_add_string(wb, "type", "actor_link");
1970 + buffer_json_object_close(wb);
1971 +
1972 + buffer_json_add_array_item_object(wb);
1973 + buffer_json_member_add_string(wb, "key", "protocol");
1974 + buffer_json_member_add_string(wb, "label", "Protocol");
1975 + buffer_json_object_close(wb);
1976 +
1977 + buffer_json_add_array_item_object(wb);
1978 + buffer_json_member_add_string(wb, "key", "direction");
1979 + buffer_json_member_add_string(wb, "label", "Direction");
1980 + buffer_json_object_close(wb);
1981 + }
1982 + buffer_json_array_close(wb);
1983 + }
1984 + buffer_json_object_close(wb);
1985 + }
1986 + buffer_json_object_close(wb);
1987 +
1988 + buffer_json_member_add_array(wb, "modal_tabs");
1989 + {
1990 + buffer_json_add_array_item_object(wb);
1991 + buffer_json_member_add_string(wb, "id", "info");
1992 + buffer_json_member_add_string(wb, "label", "Info");
1993 + buffer_json_object_close(wb);
1994 + }
1995 + buffer_json_array_close(wb);
1996 + }
1997 + buffer_json_object_close(wb);
1998 + }
1999 + buffer_json_object_close(wb);
2000 +
2001 + buffer_json_member_add_object(wb, "link_types");
2002 + {
2003 + buffer_json_member_add_object(wb, "ownership");
2004 + {
2005 + buffer_json_member_add_string(wb, "label", "Ownership");
2006 + buffer_json_member_add_string(wb, "color_slot", "muted");
2007 + buffer_json_member_add_boolean(wb, "dash", true);
2008 + }
2009 + buffer_json_object_close(wb);
2010 +
2011 + buffer_json_member_add_object(wb, "socket");
2012 + {
2013 + buffer_json_member_add_string(wb, "label", "Socket");
2014 + buffer_json_member_add_string(wb, "color_slot", "primary");
2015 + buffer_json_member_add_double(wb, "width", 1.5);
2016 + }
2017 + buffer_json_object_close(wb);
2018 + }
2019 + buffer_json_object_close(wb);
2020 +
2021 + buffer_json_member_add_array(wb, "port_fields");
2022 + {
2023 + buffer_json_add_array_item_object(wb);
2024 + buffer_json_member_add_string(wb, "key", "type");
2025 + buffer_json_member_add_string(wb, "label", "Type");
2026 + buffer_json_object_close(wb);
2027 + }
2028 + buffer_json_array_close(wb);
2029 +
2030 + buffer_json_member_add_object(wb, "port_types");
2031 + {
2032 + buffer_json_member_add_object(wb, "topology");
2033 + {
2034 + buffer_json_member_add_string(wb, "label", "Socket");
2035 + buffer_json_member_add_string(wb, "color_slot", "primary");
2036 + buffer_json_member_add_double(wb, "opacity", 1.0);
2037 + }
2038 + buffer_json_object_close(wb);
2039 + }
2040 + buffer_json_object_close(wb);
2041 +
2042 + buffer_json_member_add_object(wb, "legend");
2043 + {
2044 + buffer_json_member_add_array(wb, "actors");
2045 + {
2046 + buffer_json_add_array_item_object(wb);
2047 + buffer_json_member_add_string(wb, "type", "self");
2048 + buffer_json_member_add_string(wb, "label", "This host");
2049 + buffer_json_object_close(wb);
2050 +
2051 + buffer_json_add_array_item_object(wb);
2052 + buffer_json_member_add_string(wb, "type", "process");
2053 + buffer_json_member_add_string(wb, "label", "Process");
2054 + buffer_json_object_close(wb);
2055 +
2056 + buffer_json_add_array_item_object(wb);
2057 + buffer_json_member_add_string(wb, "type", "endpoint");
2058 + buffer_json_member_add_string(wb, "label", "Endpoint");
2059 + buffer_json_object_close(wb);
2060 + }
2061 + buffer_json_array_close(wb);
2062 +
2063 + buffer_json_member_add_array(wb, "links");
2064 + {
2065 + buffer_json_add_array_item_object(wb);
2066 + buffer_json_member_add_string(wb, "type", "ownership");
2067 + buffer_json_member_add_string(wb, "label", "Ownership");
2068 + buffer_json_object_close(wb);
2069 +
2070 + buffer_json_add_array_item_object(wb);
2071 + buffer_json_member_add_string(wb, "type", "socket");
2072 + buffer_json_member_add_string(wb, "label", "Socket");
2073 + buffer_json_object_close(wb);
2074 + }
2075 + buffer_json_array_close(wb);
2076 +
2077 + buffer_json_member_add_array(wb, "ports");
2078 + {
2079 + buffer_json_add_array_item_object(wb);
2080 + buffer_json_member_add_string(wb, "type", "topology");
2081 + buffer_json_member_add_string(wb, "label", "Socket");
2082 + buffer_json_object_close(wb);
2083 + }
2084 + buffer_json_array_close(wb);
2085 + }
2086 + buffer_json_object_close(wb);
2087 +
2088 + buffer_json_member_add_string(wb, "actor_click_behavior", "highlight_connections");
2089 }
247 - buffer_json_array_close(wb);
2090 + buffer_json_object_close(wb);
2091 }
2092
250 -static void populate_aggregated_key(const LOCAL_SOCKET *nn) {
251 - LOCAL_SOCKET *n = (LOCAL_SOCKET *)nn;
252 -
253 - n->network_viewer.count = 1;
254 -
255 - n->network_viewer.aggregated_key.pid = n->pid;
256 - n->network_viewer.aggregated_key.uid = n->uid;
257 - n->network_viewer.aggregated_key.direction = n->direction;
258 - n->network_viewer.aggregated_key.net_ns_inode = n->net_ns_inode;
259 - n->network_viewer.aggregated_key.state = n->state;
2093 +static void topology_write_actors(BUFFER *wb, const NV_TOPOLOGY_CONTEXT *ctx, NV_TOPOLOGY_RENDER_STATE *state) {
2094 + DICTIONARY *process_socket_index = topology_build_process_socket_index(ctx);
2095
261 - switch(n->direction) {
262 - case SOCKET_DIRECTION_INBOUND:
263 - case SOCKET_DIRECTION_LOCAL_INBOUND:
264 - case SOCKET_DIRECTION_LISTEN:
265 - n->network_viewer.aggregated_key.server = n->local;
266 - break;
2096 + buffer_json_member_add_array(wb, "actors");
2097 + {
2098 + buffer_json_add_array_item_object(wb);
2099 + {
2100 + buffer_json_member_add_string(wb, "actor_id", state->host_actor_id);
2101 + buffer_json_member_add_string(wb, "actor_type", "self");
2102 + buffer_json_member_add_string(wb, "layer", NETWORK_TOPOLOGY_LAYER);
2103 + buffer_json_member_add_string(wb, "source", NETWORK_TOPOLOGY_SOURCE);
2104 + topology_add_host_match(wb, ctx);
2105
268 - case SOCKET_DIRECTION_OUTBOUND:
269 - case SOCKET_DIRECTION_LOCAL_OUTBOUND:
270 - n->network_viewer.aggregated_key.server = n->remote;
271 - break;
2106 + buffer_json_member_add_object(wb, "attributes");
2107 + {
2108 + buffer_json_member_add_string(wb, "hostname", ctx->hostname);
2109 + buffer_json_member_add_uint64(wb, "local_ip_count", state->local_ip_count);
2110 + buffer_json_member_add_uint64(wb, "observed_sockets", ctx->sockets_total);
2111 + buffer_json_member_add_string(wb, "display_name", ctx->hostname);
2112 + buffer_json_member_add_string(wb, "actor_class", "self");
2113 + }
2114 + buffer_json_object_close(wb);
2115
273 - case SOCKET_DIRECTION_NONE:
274 - break;
275 - }
2116 + buffer_json_member_add_object(wb, "labels");
2117 + {
2118 + buffer_json_member_add_string(wb, "hostname", ctx->hostname);
2119 + if(ctx->machine_guid[0])
2120 + buffer_json_member_add_string(wb, "netdata_machine_guid", ctx->machine_guid);
2121 + buffer_json_member_add_string(wb, "source", NETWORK_TOPOLOGY_SOURCE);
2122 + buffer_json_member_add_string(wb, "display_name", ctx->hostname);
2123 + buffer_json_member_add_string(wb, "actor_class", "self");
2124 + }
2125 + buffer_json_object_close(wb);
2126 + }
2127 + buffer_json_object_close(wb);
2128
277 - n->network_viewer.aggregated_key.local_address_space = local_sockets_address_space(&n->local);
278 - n->network_viewer.aggregated_key.remote_address_space = local_sockets_address_space(&n->remote);
279 -}
2129 + NV_PROCESS_ACTOR *pa;
2130 + dfe_start_read(ctx->process_actors, pa) {
2131 + char process_actor_id[NV_TOPOLOGY_KEY_MAX];
2132 + char process_display_name[NV_TOPOLOGY_KEY_MAX];
2133 + topology_actor_id_for_process(ctx, pa->pid, pa->uid, pa->net_ns_inode, pa->process, process_actor_id, sizeof(process_actor_id));
2134 + topology_process_display_name(ctx, pa->process, pa->pid, process_display_name, sizeof(process_display_name));
2135 + buffer_json_add_array_item_object(wb);
2136 + {
2137 + buffer_json_member_add_string(wb, "actor_id", process_actor_id);
2138 + buffer_json_member_add_string(wb, "actor_type", "process");
2139 + buffer_json_member_add_string(wb, "layer", NETWORK_TOPOLOGY_LAYER);
2140 + buffer_json_member_add_string(wb, "source", NETWORK_TOPOLOGY_SOURCE);
2141 + topology_add_process_match(wb, ctx, pa);
2142
281 -static void local_sockets_cb_to_json(LS_STATE *ls, const LOCAL_SOCKET *n, void *data) {
282 - struct sockets_stats *st = data;
283 - populate_aggregated_key(n);
284 - local_socket_to_json_array(st, n, ls->proc_self_net_ns_inode, false);
285 -}
2143 + buffer_json_member_add_object(wb, "parent_match");
2144 + {
2145 + if(ctx->machine_guid[0])
2146 + buffer_json_member_add_string(wb, "netdata_machine_guid", ctx->machine_guid);
2147 + topology_add_single_item_string_array(wb, "hostnames", ctx->hostname);
2148 + }
2149 + buffer_json_object_close(wb);
2150
287 -#define KEEP_THE_BIGGER(a, b) (a) = ((a) < (b)) ? (b) : (a)
288 -#define KEEP_THE_SMALLER(a, b) (a) = ((a) > (b)) ? (b) : (a)
289 -#define SUM_THEM_ALL(a, b) (a) += (b)
290 -#define OR_THEM_ALL(a, b) (a) |= (b)
2151 + buffer_json_member_add_object(wb, "attributes");
2152 + {
2153 + if(ctx->options.processes_by_pid) {
2154 + buffer_json_member_add_uint64(wb, "pid", pa->pid);
2155 + buffer_json_member_add_uint64(wb, "ppid", pa->ppid);
2156 + buffer_json_member_add_uint64(wb, "uid", pa->uid);
2157 + buffer_json_member_add_uint64(wb, "net_ns_inode", pa->net_ns_inode);
2158 + }
2159 + buffer_json_member_add_uint64(wb, "socket_count", pa->sockets);
2160 + buffer_json_member_add_string(wb, "local_ip", pa->local_ip);
2161 + buffer_json_member_add_string(wb, "local_address_space", pa->local_address_space);
2162 + buffer_json_member_add_string(wb, "display_name", process_display_name);
2163 + buffer_json_member_add_string(wb, "actor_class", "process");
2164 + if(pa->cmdline[0])
2165 + buffer_json_member_add_string(wb, "cmdline", pa->cmdline);
2166 + }
2167 + buffer_json_object_close(wb);
2168
292 -static void local_sockets_cb_to_aggregation(LS_STATE *ls __maybe_unused, const LOCAL_SOCKET *n, void *data) {
293 - SIMPLE_HASHTABLE_AGGREGATED_SOCKETS *ht = data;
2169 + buffer_json_member_add_object(wb, "labels");
2170 + {
2171 + buffer_json_member_add_string(wb, "process", pa->process);
2172 + buffer_json_member_add_string(wb, "user", pa->username);
2173 + buffer_json_member_add_string(wb, "namespace", pa->namespace_type);
2174 + buffer_json_member_add_string(wb, "local_address_space", pa->local_address_space);
2175 + buffer_json_member_add_string(wb, "display_name", process_display_name);
2176 + buffer_json_member_add_string(wb, "actor_class", "process");
2177 + }
2178 + buffer_json_object_close(wb);
2179
295 - populate_aggregated_key(n);
296 - XXH64_hash_t hash = XXH3_64bits(&n->network_viewer.aggregated_key, sizeof(n->network_viewer.aggregated_key));
297 - SIMPLE_HASHTABLE_SLOT_AGGREGATED_SOCKETS *sl = simple_hashtable_get_slot_AGGREGATED_SOCKETS(ht, hash, (LOCAL_SOCKET *)n, true);
298 - LOCAL_SOCKET *t = SIMPLE_HASHTABLE_SLOT_DATA(sl);
299 - if(t) {
300 - t->network_viewer.count++;
2180 + buffer_json_member_add_object(wb, "tables");
2181 + {
2182 + buffer_json_member_add_array(wb, "sockets");
2183 + {
2184 + NV_PROCESS_SOCKET_ROWS *rows = process_socket_index ? dictionary_get(process_socket_index, process_actor_id) : NULL;
2185 + for(NV_PROCESS_SOCKET_ROW *row = rows ? rows->head : NULL; row; row = row->next) {
2186 + char remote_endpoint[128];
2187 + topology_format_ip_port(row->link->remote_ip, row->link->remote_port, remote_endpoint, sizeof(remote_endpoint));
2188 + buffer_json_add_array_item_object(wb);
2189 + buffer_json_member_add_string(wb, "remote", remote_endpoint);
2190 + buffer_json_member_add_string(wb, "protocol", row->link->protocol);
2191 + buffer_json_member_add_string(wb, "direction", row->link->direction);
2192 + buffer_json_member_add_string(wb, "state", row->link->state);
2193 + buffer_json_object_close(wb);
2194 + }
2195 + }
2196 + buffer_json_array_close(wb);
2197 + }
2198 + buffer_json_object_close(wb);
2199 + }
2200 + buffer_json_object_close(wb);
2201 + }
2202 + dfe_done(pa);
2203
302 - KEEP_THE_BIGGER(t->timer, n->timer);
303 - KEEP_THE_BIGGER(t->retransmits, n->retransmits);
304 - KEEP_THE_SMALLER(t->expires, n->expires);
305 - KEEP_THE_BIGGER(t->rqueue, n->rqueue);
306 - KEEP_THE_BIGGER(t->wqueue, n->wqueue);
2204 + NV_REMOTE_ACTOR *ra;
2205 + dfe_start_read(ctx->remote_actors, ra) {
2206 + bool endpoint_is_self = topology_ip_belongs_to_self(ctx, ra->ip, ra->address_space);
2207
308 - // The current number of consecutive retransmissions that have occurred for the most recently transmitted segment.
309 - SUM_THEM_ALL(t->info.tcp.tcpi_retransmits, n->info.tcp.tcpi_retransmits);
2208 + char endpoint_actor_id[NV_TOPOLOGY_KEY_MAX];
2209 + topology_actor_id_for_remote_endpoint(ctx, ra->ip, ra->address_space, endpoint_actor_id, sizeof(endpoint_actor_id));
2210 + state->endpoint_actor_count++;
2211 + buffer_json_add_array_item_object(wb);
2212 + {
2213 + buffer_json_member_add_string(wb, "actor_id", endpoint_actor_id);
2214 + buffer_json_member_add_string(wb, "actor_type", "endpoint");
2215 + buffer_json_member_add_string(wb, "layer", NETWORK_TOPOLOGY_LAYER);
2216 + buffer_json_member_add_string(wb, "source", NETWORK_TOPOLOGY_SOURCE);
2217 + topology_add_remote_match(wb, ra->ip);
2218 +
2219 + if(endpoint_is_self) {
2220 + buffer_json_member_add_object(wb, "parent_match");
2221 + {
2222 + if(ctx->machine_guid[0])
2223 + buffer_json_member_add_string(wb, "netdata_machine_guid", ctx->machine_guid);
2224 + topology_add_single_item_string_array(wb, "hostnames", ctx->hostname);
2225 + }
2226 + buffer_json_object_close(wb);
2227 + }
2228
311 - // The total number of retransmissions that have occurred for the entire connection since it was established.
312 - SUM_THEM_ALL(t->info.tcp.tcpi_total_retrans, n->info.tcp.tcpi_total_retrans);
2229 + buffer_json_member_add_object(wb, "attributes");
2230 + {
2231 + buffer_json_member_add_uint64(wb, "socket_count", ra->sockets);
2232 + buffer_json_member_add_uint64(wb, "local_socket_count", endpoint_is_self ? ra->sockets : 0);
2233 + buffer_json_member_add_uint64(wb, "remote_socket_count", endpoint_is_self ? 0 : ra->sockets);
2234 + buffer_json_member_add_string(wb, "endpoint_scope", endpoint_is_self ? "self" : "remote");
2235 + buffer_json_member_add_string(wb, "display_name", ra->ip);
2236 + buffer_json_member_add_string(wb, "actor_class", "endpoint");
2237 + }
2238 + buffer_json_object_close(wb);
2239
314 - // The total number of segments that have been retransmitted since the connection was established.
315 - SUM_THEM_ALL(t->info.tcp.tcpi_retrans, n->info.tcp.tcpi_retrans);
2240 + buffer_json_member_add_object(wb, "labels");
2241 + {
2242 + buffer_json_member_add_string(wb, "address_space", ra->address_space);
2243 + buffer_json_member_add_string(wb, "endpoint_scope", endpoint_is_self ? "self" : "remote");
2244 + buffer_json_member_add_string(wb, "display_name", ra->ip);
2245 + buffer_json_member_add_string(wb, "actor_class", "endpoint");
2246 + }
2247 + buffer_json_object_close(wb);
2248 + }
2249 + buffer_json_object_close(wb);
2250 + }
2251 + dfe_done(ra);
2252 + }
2253 + buffer_json_array_close(wb);
2254
317 - // The number of keepalive probes sent
318 - SUM_THEM_ALL(t->info.tcp.tcpi_probes, n->info.tcp.tcpi_probes);
2255 + topology_destroy_process_socket_index(process_socket_index);
2256 +}
2257
320 - // The number of times the retransmission timeout has been backed off.
321 - SUM_THEM_ALL(t->info.tcp.tcpi_backoff, n->info.tcp.tcpi_backoff);
2258 +static void topology_write_links_and_stats(BUFFER *wb, const NV_TOPOLOGY_CONTEXT *ctx, NV_TOPOLOGY_RENDER_STATE *state) {
2259 + buffer_json_member_add_array(wb, "links");
2260 + {
2261 + DICTIONARY *process_parent_ns_lookup = NULL;
2262 + DICTIONARY *process_parent_any_lookup = NULL;
2263 + DICTIONARY *ppid_cache = NULL;
2264 + if(ctx->options.processes_by_pid) {
2265 + process_parent_ns_lookup = dictionary_create_advanced(
2266 + DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
2267 + NULL, sizeof(NV_ENDPOINT_OWNER));
2268 + process_parent_any_lookup = dictionary_create_advanced(
2269 + DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
2270 + NULL, sizeof(NV_ENDPOINT_OWNER));
2271 + ppid_cache = dictionary_create_advanced(
2272 + DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
2273 + NULL, sizeof(NV_PPID_CACHE_ENTRY));
2274 +
2275 + if(process_parent_ns_lookup && process_parent_any_lookup) {
2276 + char parent_key_ns[NV_TOPOLOGY_KEY_MAX];
2277 + char parent_key_any[NV_TOPOLOGY_KEY_MAX];
2278 + char pid_key[64];
2279 + NV_PROCESS_ACTOR *pa_index;
2280 + dfe_start_read(ctx->process_actors, pa_index) {
2281 + if(!pa_index->pid)
2282 + continue;
2283 +
2284 + NV_ENDPOINT_OWNER owner = {
2285 + .pid = pa_index->pid,
2286 + .ppid = pa_index->ppid,
2287 + .uid = pa_index->uid,
2288 + .net_ns_inode = pa_index->net_ns_inode,
2289 + };
2290 + snprintf(owner.process, sizeof(owner.process), "%s", pa_index->process);
2291 +
2292 + topology_process_parent_lookup_key(parent_key_ns, sizeof(parent_key_ns),
2293 + (uint64_t)pa_index->pid, pa_index->net_ns_inode, true);
2294 + dictionary_set(process_parent_ns_lookup, parent_key_ns, &owner, sizeof(owner));
2295 +
2296 + topology_process_parent_lookup_key(parent_key_any, sizeof(parent_key_any),
2297 + (uint64_t)pa_index->pid, 0, false);
2298 + dictionary_set(process_parent_any_lookup, parent_key_any, &owner, sizeof(owner));
2299 +
2300 + if(ppid_cache) {
2301 + NV_PPID_CACHE_ENTRY ppid_entry = { .ppid = pa_index->ppid };
2302 + topology_pid_lookup_key(pid_key, sizeof(pid_key), (uint64_t)pa_index->pid);
2303 + dictionary_set(ppid_cache, pid_key, &ppid_entry, sizeof(ppid_entry));
2304 + }
2305 + }
2306 + dfe_done(pa_index);
2307 + }
2308 + }
2309
323 - // A bitmask representing the TCP options currently enabled for the connection, such as SACK and Timestamps.
324 - OR_THEM_ALL(t->info.tcp.tcpi_options, n->info.tcp.tcpi_options);
2310 + NV_PROCESS_ACTOR *pa;
2311 + dfe_start_read(ctx->process_actors, pa) {
2312 + bool src_is_process = false;
2313 + NV_ENDPOINT_OWNER *parent_pa = NULL;
2314 + NV_ENDPOINT_OWNER parent_resolved = { 0 };
2315 + const char *ownership_kind = "self_root";
2316 + char src_actor_id[NV_TOPOLOGY_KEY_MAX];
2317 + char src_display_name[NV_TOPOLOGY_KEY_MAX];
2318 + char process_actor_id[NV_TOPOLOGY_KEY_MAX];
2319 + char process_display_name[NV_TOPOLOGY_KEY_MAX];
2320 + char ownership_display_name[NV_TOPOLOGY_KEY_MAX * 2 + 7];
2321 + topology_actor_id_for_process(ctx, pa->pid, pa->uid, pa->net_ns_inode, pa->process, process_actor_id, sizeof(process_actor_id));
2322 + topology_process_display_name(ctx, pa->process, pa->pid, process_display_name, sizeof(process_display_name));
2323 +
2324 + if(ctx->options.processes_by_pid &&
2325 + process_parent_ns_lookup && process_parent_any_lookup &&
2326 + pa->ppid && pa->ppid != pa->pid) {
2327 + parent_pa = topology_find_process_parent_actor(pa->ppid, pa->net_ns_inode,
2328 + process_parent_ns_lookup,
2329 + process_parent_any_lookup,
2330 + ppid_cache);
2331 +
2332 + if(parent_pa) {
2333 + src_is_process = true;
2334 + ownership_kind = "process_parent";
2335 + parent_resolved = *parent_pa;
2336 + parent_pa = &parent_resolved;
2337 + topology_actor_id_for_process(ctx, parent_pa->pid, parent_pa->uid, parent_pa->net_ns_inode, parent_pa->process, src_actor_id, sizeof(src_actor_id));
2338 + topology_process_display_name(ctx, parent_pa->process, parent_pa->pid, src_display_name, sizeof(src_display_name));
2339 + }
2340 + }
2341
326 - // The send window scale value used for this connection
327 - KEEP_THE_SMALLER(t->info.tcp.tcpi_snd_wscale, n->info.tcp.tcpi_snd_wscale);
2342 + if(!src_is_process) {
2343 + snprintf(src_actor_id, sizeof(src_actor_id), "%s", state->host_actor_id);
2344 + snprintf(src_display_name, sizeof(src_display_name), "%s", ctx->hostname);
2345 + }
2346
329 - // The receive window scale value used for this connection
330 - KEEP_THE_SMALLER(t->info.tcp.tcpi_rcv_wscale, n->info.tcp.tcpi_rcv_wscale);
2347 + snprintf(ownership_display_name, sizeof(ownership_display_name), "%s owns %s", src_display_name, process_display_name);
2348 + state->ownership_link_count++;
2349
332 - // Retransmission timeout in milliseconds
333 - KEEP_THE_SMALLER(t->info.tcp.tcpi_rto, n->info.tcp.tcpi_rto);
2350 + buffer_json_add_array_item_object(wb);
2351 + {
2352 + buffer_json_member_add_string(wb, "layer", NETWORK_TOPOLOGY_LAYER);
2353 + buffer_json_member_add_string(wb, "protocol", "ownership");
2354 + buffer_json_member_add_string(wb, "link_type", "ownership");
2355 + buffer_json_member_add_string(wb, "direction", "contains");
2356 + buffer_json_member_add_string(wb, "state", "active");
2357 + buffer_json_member_add_string(wb, "src_actor_id", src_actor_id);
2358 + buffer_json_member_add_string(wb, "dst_actor_id", process_actor_id);
2359 + buffer_json_member_add_datetime_rfc3339(wb, "discovered_at", ctx->now_ut, true);
2360 + buffer_json_member_add_datetime_rfc3339(wb, "last_seen", ctx->now_ut, true);
2361 +
2362 + buffer_json_member_add_object(wb, "src");
2363 + {
2364 + if(src_is_process) {
2365 + topology_add_process_identity_match(wb, ctx, parent_pa->pid, parent_pa->uid, parent_pa->net_ns_inode, parent_pa->process);
2366 +
2367 + buffer_json_member_add_object(wb, "attributes");
2368 + {
2369 + buffer_json_member_add_string(wb, "actor_type", "process");
2370 + if(ctx->options.processes_by_pid) {
2371 + buffer_json_member_add_uint64(wb, "pid", parent_pa->pid);
2372 + buffer_json_member_add_uint64(wb, "ppid", parent_pa->ppid);
2373 + buffer_json_member_add_uint64(wb, "uid", parent_pa->uid);
2374 + buffer_json_member_add_uint64(wb, "net_ns_inode", parent_pa->net_ns_inode);
2375 + }
2376 + buffer_json_member_add_string(wb, "process", parent_pa->process);
2377 + buffer_json_member_add_string(wb, "display_name", src_display_name);
2378 + }
2379 + buffer_json_object_close(wb);
2380 + }
2381 + else {
2382 + topology_add_host_match(wb, ctx);
2383 +
2384 + buffer_json_member_add_object(wb, "attributes");
2385 + {
2386 + buffer_json_member_add_string(wb, "actor_type", "self");
2387 + buffer_json_member_add_string(wb, "display_name", ctx->hostname);
2388 + }
2389 + buffer_json_object_close(wb);
2390 + }
2391 + }
2392 + buffer_json_object_close(wb);
2393
335 - // The delayed acknowledgement timeout in milliseconds.
336 - KEEP_THE_SMALLER(t->info.tcp.tcpi_ato, n->info.tcp.tcpi_ato);
2394 + buffer_json_member_add_object(wb, "dst");
2395 + {
2396 + topology_add_process_match(wb, ctx, pa);
2397
338 - // The maximum segment size for sending.
339 - KEEP_THE_SMALLER(t->info.tcp.tcpi_snd_mss, n->info.tcp.tcpi_snd_mss);
2398 + buffer_json_member_add_object(wb, "attributes");
2399 + {
2400 + buffer_json_member_add_string(wb, "actor_type", "process");
2401 + if(ctx->options.processes_by_pid) {
2402 + buffer_json_member_add_uint64(wb, "pid", pa->pid);
2403 + buffer_json_member_add_uint64(wb, "ppid", pa->ppid);
2404 + buffer_json_member_add_uint64(wb, "uid", pa->uid);
2405 + buffer_json_member_add_uint64(wb, "net_ns_inode", pa->net_ns_inode);
2406 + }
2407 + buffer_json_member_add_string(wb, "process", pa->process);
2408 + buffer_json_member_add_string(wb, "display_name", process_display_name);
2409 + }
2410 + buffer_json_object_close(wb);
2411 + }
2412 + buffer_json_object_close(wb);
2413
341 - // The maximum segment size for receiving.
342 - KEEP_THE_SMALLER(t->info.tcp.tcpi_rcv_mss, n->info.tcp.tcpi_rcv_mss);
2414 + buffer_json_member_add_object(wb, "metrics");
2415 + {
2416 + buffer_json_member_add_uint64(wb, "socket_count", pa->sockets);
2417 + buffer_json_member_add_string(wb, "display_name", ownership_display_name);
2418 + }
2419 + buffer_json_object_close(wb);
2420
344 - // The number of unacknowledged segments
345 - SUM_THEM_ALL(t->info.tcp.tcpi_unacked, n->info.tcp.tcpi_unacked);
2421 + buffer_json_member_add_object(wb, "labels");
2422 + {
2423 + buffer_json_member_add_string(wb, "link_class", "ownership");
2424 + buffer_json_member_add_string(wb, "render_intent", "dark");
2425 + buffer_json_member_add_string(wb, "ownership_kind", ownership_kind);
2426 + buffer_json_member_add_string(wb, "display_name", ownership_display_name);
2427 + }
2428 + buffer_json_object_close(wb);
2429 + }
2430 + buffer_json_object_close(wb);
2431 + }
2432 + dfe_done(pa);
2433 +
2434 + if(process_parent_ns_lookup)
2435 + dictionary_destroy(process_parent_ns_lookup);
2436 + if(process_parent_any_lookup)
2437 + dictionary_destroy(process_parent_any_lookup);
2438 + if(ppid_cache)
2439 + dictionary_destroy(ppid_cache);
2440 +
2441 + NV_TOPOLOGY_LINK *link;
2442 + dfe_start_read(ctx->links, link) {
2443 + char process_actor_id[NV_TOPOLOGY_KEY_MAX];
2444 + char dst_actor_id[NV_TOPOLOGY_KEY_MAX];
2445 + char endpoint_actor_id[NV_TOPOLOGY_KEY_MAX];
2446 + char local_bind_port[128];
2447 + char remote_endpoint_port[128];
2448 + char process_display_name[NV_TOPOLOGY_KEY_MAX];
2449 + char dst_process_display_name[NV_TOPOLOGY_KEY_MAX];
2450 + char dst_process_port_name[64] = "";
2451 + char link_display_name[NV_TOPOLOGY_KEY_MAX * 2 + 256 + 7];
2452 + bool remote_is_self = topology_ip_belongs_to_self(ctx, link->remote_ip, link->remote_address_space);
2453 + bool dst_is_process = false;
2454 + bool dst_local_peer_unresolved = false;
2455 + uint64_t dst_pid = link->pid;
2456 + uint64_t dst_ppid = link->ppid;
2457 + uint64_t dst_uid = link->uid;
2458 + uint64_t dst_net_ns_inode = link->net_ns_inode;
2459 + const char *dst_process_name = link->process;
2460 + uint16_t dst_process_port = 0;
2461 +
2462 + topology_actor_id_for_process(ctx, link->pid, link->uid, link->net_ns_inode, link->process, process_actor_id, sizeof(process_actor_id));
2463 + topology_actor_id_for_remote_endpoint(ctx, link->remote_ip, link->remote_address_space, endpoint_actor_id, sizeof(endpoint_actor_id));
2464 + snprintf(local_bind_port, sizeof(local_bind_port), "%u", link->local_port);
2465 + topology_format_ip_port(link->remote_ip, link->remote_port, remote_endpoint_port, sizeof(remote_endpoint_port));
2466 + topology_process_display_name(ctx, link->process, link->pid, process_display_name, sizeof(process_display_name));
2467 +
2468 + if(remote_is_self && link->direction_id != SOCKET_DIRECTION_LISTEN) {
2469 + const char *peer_ip = link->peer_ip[0] ? link->peer_ip : link->remote_ip;
2470 + bool allow_service_fallback = true;
2471 + NV_ENDPOINT_OWNER *owner = topology_lookup_endpoint_owner(ctx, link->net_ns_inode, link->protocol_id, peer_ip, link->peer_port, allow_service_fallback);
2472 +
2473 + if(owner) {
2474 + dst_pid = owner->pid;
2475 + dst_ppid = owner->ppid;
2476 + dst_uid = owner->uid;
2477 + dst_net_ns_inode = owner->net_ns_inode;
2478 + dst_process_name = owner->process;
2479 + dst_process_port = link->peer_port;
2480 + }
2481 + else {
2482 + dst_local_peer_unresolved = (link->direction_id != SOCKET_DIRECTION_LISTEN);
2483 + dst_process_port = (link->direction_id == SOCKET_DIRECTION_LISTEN) ? link->local_port : (link->peer_port ? link->peer_port : link->remote_port);
2484 + }
2485
347 - // The number of segments that have been selectively acknowledged
348 - SUM_THEM_ALL(t->info.tcp.tcpi_sacked, n->info.tcp.tcpi_sacked);
2486 + dst_is_process = true;
2487 + topology_actor_id_for_process(ctx, dst_pid, dst_uid, dst_net_ns_inode, dst_process_name, dst_actor_id, sizeof(dst_actor_id));
2488 + topology_process_display_name(ctx, dst_process_name, dst_pid, dst_process_display_name, sizeof(dst_process_display_name));
2489 + if(dst_process_port)
2490 + snprintf(dst_process_port_name, sizeof(dst_process_port_name), "%u", dst_process_port);
2491 + else
2492 + snprintf(dst_process_port_name, sizeof(dst_process_port_name), "unknown");
2493
350 - // The number of segments that have been selectively acknowledged
351 - SUM_THEM_ALL(t->info.tcp.tcpi_sacked, n->info.tcp.tcpi_sacked);
2494 + snprintf(link_display_name, sizeof(link_display_name), "%s:%s -> %s:%s",
2495 + process_display_name, local_bind_port, dst_process_display_name, dst_process_port_name);
2496 + }
2497 + else {
2498 + snprintf(dst_actor_id, sizeof(dst_actor_id), "%s", endpoint_actor_id);
2499 + snprintf(link_display_name, sizeof(link_display_name), "%s:%s -> %s",
2500 + process_display_name, local_bind_port, remote_endpoint_port);
2501 + }
2502
353 - // The number of lost segments.
354 - SUM_THEM_ALL(t->info.tcp.tcpi_lost, n->info.tcp.tcpi_lost);
2503 + buffer_json_add_array_item_object(wb);
2504 + {
2505 + buffer_json_member_add_string(wb, "layer", NETWORK_TOPOLOGY_LAYER);
2506 + buffer_json_member_add_string(wb, "protocol", link->protocol);
2507 + buffer_json_member_add_string(wb, "link_type", "socket");
2508 + buffer_json_member_add_string(wb, "direction", link->direction);
2509 + buffer_json_member_add_string(wb, "state", link->state);
2510 + buffer_json_member_add_string(wb, "src_actor_id", process_actor_id);
2511 + buffer_json_member_add_string(wb, "dst_actor_id", dst_actor_id);
2512 + buffer_json_member_add_datetime_rfc3339(wb, "discovered_at", ctx->now_ut, true);
2513 + buffer_json_member_add_datetime_rfc3339(wb, "last_seen", ctx->now_ut, true);
2514 +
2515 + buffer_json_member_add_object(wb, "src");
2516 + {
2517 + buffer_json_member_add_object(wb, "match");
2518 + {
2519 + if(ctx->machine_guid[0])
2520 + buffer_json_member_add_string(wb, "netdata_machine_guid", ctx->machine_guid);
2521 + topology_add_single_item_string_array(wb, "hostnames", ctx->hostname);
2522 + topology_add_single_item_string_array(wb, "ip_addresses", link->local_ip);
2523 + }
2524 + buffer_json_object_close(wb);
2525
356 - // The number of forward acknowledgment segments.
357 - SUM_THEM_ALL(t->info.tcp.tcpi_fackets, n->info.tcp.tcpi_fackets);
2526 + buffer_json_member_add_object(wb, "attributes");
2527 + {
2528 + buffer_json_member_add_string(wb, "actor_type", "process");
2529 + if(ctx->options.processes_by_pid) {
2530 + buffer_json_member_add_uint64(wb, "pid", link->pid);
2531 + buffer_json_member_add_uint64(wb, "ppid", link->ppid);
2532 + buffer_json_member_add_uint64(wb, "uid", link->uid);
2533 + buffer_json_member_add_uint64(wb, "net_ns_inode", link->net_ns_inode);
2534 + }
2535 + buffer_json_member_add_string(wb, "process", link->process);
2536 + buffer_json_member_add_string(wb, "user", link->username);
2537 + buffer_json_member_add_string(wb, "namespace", link->namespace_type);
2538 + buffer_json_member_add_string(wb, "address_space", link->local_address_space);
2539 + buffer_json_member_add_uint64(wb, "port", link->local_port);
2540 + buffer_json_member_add_string(wb, "port_name", local_bind_port);
2541 + buffer_json_member_add_string(wb, "bind_ip", link->local_ip);
2542 + buffer_json_member_add_string(wb, "service_name", link->port_name);
2543 + buffer_json_member_add_string(wb, "display_name", process_display_name);
2544 + buffer_json_member_add_string(wb, "protocol_family", link->protocol_family);
2545 + if(link->cmdline[0])
2546 + buffer_json_member_add_string(wb, "cmdline", link->cmdline);
2547 + }
2548 + buffer_json_object_close(wb);
2549 + }
2550 + buffer_json_object_close(wb);
2551
359 - // The time in milliseconds since the last data was sent.
360 - KEEP_THE_SMALLER(t->info.tcp.tcpi_last_data_sent, n->info.tcp.tcpi_last_data_sent);
2552 + buffer_json_member_add_object(wb, "dst");
2553 + {
2554 + if(dst_is_process)
2555 + topology_add_process_identity_match(wb, ctx, dst_pid, dst_uid, dst_net_ns_inode, dst_process_name);
2556 + else
2557 + topology_add_remote_match(wb, link->remote_ip);
2558
362 - // The time in milliseconds since the last acknowledgment was sent (not tracked in Linux, hence often zero).
363 - KEEP_THE_SMALLER(t->info.tcp.tcpi_last_ack_sent, n->info.tcp.tcpi_last_ack_sent);
2559 + buffer_json_member_add_object(wb, "attributes");
2560 + {
2561 + if(dst_is_process) {
2562 + buffer_json_member_add_string(wb, "actor_type", "process");
2563 + if(ctx->options.processes_by_pid) {
2564 + buffer_json_member_add_uint64(wb, "pid", dst_pid);
2565 + buffer_json_member_add_uint64(wb, "ppid", dst_ppid);
2566 + buffer_json_member_add_uint64(wb, "uid", dst_uid);
2567 + buffer_json_member_add_uint64(wb, "net_ns_inode", dst_net_ns_inode);
2568 + }
2569 + buffer_json_member_add_string(wb, "process", dst_process_name);
2570 + buffer_json_member_add_string(wb, "address_space", "self");
2571 + if(dst_process_port) {
2572 + buffer_json_member_add_uint64(wb, "port", dst_process_port);
2573 + buffer_json_member_add_string(wb, "port_name", dst_process_port_name);
2574 + }
2575 + buffer_json_member_add_string(wb, "display_name", dst_process_display_name);
2576 + if(dst_local_peer_unresolved)
2577 + buffer_json_member_add_boolean(wb, "unresolved_local_peer", true);
2578 + }
2579 + else {
2580 + buffer_json_member_add_string(wb, "actor_type", "endpoint");
2581 + buffer_json_member_add_string(wb, "address_space", link->remote_address_space);
2582 + buffer_json_member_add_uint64(wb, "port", link->remote_port);
2583 + buffer_json_member_add_string(wb, "port_name", remote_endpoint_port);
2584 + buffer_json_member_add_string(wb, "display_name", link->remote_ip);
2585 + }
2586 + }
2587 + buffer_json_object_close(wb);
2588 + }
2589 + buffer_json_object_close(wb);
2590
365 - // The time in milliseconds since the last data was received.
366 - KEEP_THE_SMALLER(t->info.tcp.tcpi_last_data_recv, n->info.tcp.tcpi_last_data_recv);
2591 + buffer_json_member_add_object(wb, "metrics");
2592 + {
2593 + buffer_json_member_add_uint64(wb, "socket_count", link->sockets);
2594 + buffer_json_member_add_uint64(wb, "retransmissions", link->retransmissions);
2595 + buffer_json_member_add_double(wb, "rtt_ms_max", (double)link->max_rtt_usec / (double)USEC_PER_MS);
2596 + buffer_json_member_add_double(wb, "recv_rtt_ms_max", (double)link->max_rcv_rtt_usec / (double)USEC_PER_MS);
2597 + buffer_json_member_add_string(wb, "display_name", link_display_name);
2598 + }
2599 + buffer_json_object_close(wb);
2600
368 - // The time in milliseconds since the last acknowledgment was received.
369 - KEEP_THE_SMALLER(t->info.tcp.tcpi_last_ack_recv, n->info.tcp.tcpi_last_ack_recv);
2601 + buffer_json_member_add_object(wb, "labels");
2602 + {
2603 + buffer_json_member_add_string(wb, "protocol", link->protocol);
2604 + buffer_json_member_add_string(wb, "direction", link->direction);
2605 + buffer_json_member_add_string(wb, "state", link->state);
2606 + buffer_json_member_add_string(wb, "process", link->process);
2607 + buffer_json_member_add_string(wb, "user", link->username);
2608 + buffer_json_member_add_string(wb, "namespace", link->namespace_type);
2609 + buffer_json_member_add_string(wb, "protocol_family", link->protocol_family);
2610 + buffer_json_member_add_string(wb, "local_address_space", link->local_address_space);
2611 + buffer_json_member_add_string(wb, "remote_address_space", link->remote_address_space);
2612 + buffer_json_member_add_string(wb, "port_name", local_bind_port);
2613 + buffer_json_member_add_string(wb, "bind_ip", link->local_ip);
2614 + buffer_json_member_add_string(wb, "service_name", link->port_name);
2615 + buffer_json_member_add_string(wb, "link_class", "socket");
2616 + buffer_json_member_add_string(wb, "socket_kind", link->direction);
2617 + buffer_json_member_add_string(wb, "render_intent", "socket");
2618 + buffer_json_member_add_string(wb, "display_name", link_display_name);
2619 + }
2620 + buffer_json_object_close(wb);
2621 + }
2622 + buffer_json_object_close(wb);
2623 + }
2624 + dfe_done(link);
2625 + }
2626 + buffer_json_array_close(wb);
2627
371 - // The path MTU for this connection
372 - KEEP_THE_SMALLER(t->info.tcp.tcpi_pmtu, n->info.tcp.tcpi_pmtu);
2628 + buffer_json_member_add_object(wb, "stats");
2629 + {
2630 + size_t links_total = state->socket_link_count + state->ownership_link_count;
2631 + buffer_json_member_add_string(wb, "processes_mode", ctx->options.processes_by_pid ? "by_pid" : "by_name");
2632 + buffer_json_member_add_boolean(wb, "sockets_listening", ctx->options.sockets_listening);
2633 + buffer_json_member_add_boolean(wb, "sockets_local", ctx->options.sockets_local);
2634 + buffer_json_member_add_boolean(wb, "sockets_inbound", ctx->options.sockets_inbound);
2635 + buffer_json_member_add_boolean(wb, "sockets_outbound", ctx->options.sockets_outbound);
2636 + buffer_json_member_add_string(wb, "endpoints_mode_selected", "by_ip");
2637 + buffer_json_member_add_string(wb, "endpoints_mode_effective", "by_ip");
2638 + buffer_json_member_add_boolean(wb, "protocol_ipv4_tcp", ctx->options.protocols_ipv4_tcp);
2639 + buffer_json_member_add_boolean(wb, "protocol_ipv6_tcp", ctx->options.protocols_ipv6_tcp);
2640 + buffer_json_member_add_boolean(wb, "protocol_ipv4_udp", ctx->options.protocols_ipv4_udp);
2641 + buffer_json_member_add_boolean(wb, "protocol_ipv6_udp", ctx->options.protocols_ipv6_udp);
2642 + buffer_json_member_add_uint64(wb, "sockets_total", ctx->sockets_total);
2643 + buffer_json_member_add_uint64(wb, "sockets_without_remote_endpoint", ctx->skipped_sockets);
2644 + buffer_json_member_add_uint64(wb, "local_process_actors", state->process_actor_count);
2645 + buffer_json_member_add_uint64(wb, "endpoint_actors", state->endpoint_actor_count);
2646 + buffer_json_member_add_uint64(wb, "socket_links", state->socket_link_count);
2647 + buffer_json_member_add_uint64(wb, "ownership_links", state->ownership_link_count);
2648 + buffer_json_member_add_uint64(wb, "links_total", links_total);
2649 + }
2650 + buffer_json_object_close(wb);
2651 +}
2652
374 - // The slow start threshold for receiving
375 - KEEP_THE_SMALLER(t->info.tcp.tcpi_rcv_ssthresh, n->info.tcp.tcpi_rcv_ssthresh);
2653 +static void topology_write_data(BUFFER *wb, const NV_TOPOLOGY_CONTEXT *ctx) {
2654 + if(!ctx || ctx->options.info_only || !ctx->process_actors || !ctx->remote_actors || !ctx->local_ips || !ctx->links)
2655 + return;
2656
377 - // The slow start threshold for sending
378 - KEEP_THE_SMALLER(t->info.tcp.tcpi_snd_ssthresh, n->info.tcp.tcpi_snd_ssthresh);
2657 + NV_TOPOLOGY_RENDER_STATE state;
2658 + topology_render_state_init(&state, ctx);
2659
380 - // The round trip time in milliseconds
381 - KEEP_THE_BIGGER(t->info.tcp.tcpi_rtt, n->info.tcp.tcpi_rtt);
2660 + buffer_json_member_add_object(wb, "data");
2661 + {
2662 + buffer_json_member_add_string(wb, "schema_version", NETWORK_TOPOLOGY_SCHEMA_VERSION);
2663 + buffer_json_member_add_string(wb, "source", NETWORK_TOPOLOGY_SOURCE);
2664 + buffer_json_member_add_string(wb, "layer", NETWORK_TOPOLOGY_LAYER);
2665 + buffer_json_member_add_string(wb, "agent_id", ctx->machine_guid[0] ? ctx->machine_guid : ctx->hostname);
2666 + buffer_json_member_add_datetime_rfc3339(wb, "collected_at", ctx->now_ut, true);
2667 +
2668 + topology_write_actors(wb, ctx, &state);
2669 + topology_write_links_and_stats(wb, ctx, &state);
2670 + }
2671 + buffer_json_object_close(wb);
2672 +}
2673
383 - // The round trip time variance in milliseconds.
384 - KEEP_THE_BIGGER(t->info.tcp.tcpi_rttvar, n->info.tcp.tcpi_rttvar);
2674 +static void network_viewer_topology_function(
2675 + const char *transaction, char *function, usec_t *stop_monotonic_ut __maybe_unused,
2676 + bool *cancelled __maybe_unused, BUFFER *payload __maybe_unused, HTTP_ACCESS access __maybe_unused,
2677 + const char *source __maybe_unused, void *data __maybe_unused) {
2678
386 - // The size of the sending congestion window.
387 - KEEP_THE_SMALLER(t->info.tcp.tcpi_snd_cwnd, n->info.tcp.tcpi_snd_cwnd);
2679 + time_t now_s = now_realtime_sec();
2680 + usec_t now_ut = now_realtime_usec();
2681 + NV_TOPOLOGY_OPTIONS options = { 0 };
2682 + topology_parse_options(function, &options);
2683
389 - // The maximum segment size that could be advertised.
390 - KEEP_THE_BIGGER(t->info.tcp.tcpi_advmss, n->info.tcp.tcpi_advmss);
2684 + CLEAN_BUFFER *wb = buffer_create(0, NULL);
2685 + buffer_flush(wb);
2686 + wb->content_type = CT_APPLICATION_JSON;
2687 + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
2688
392 - // The reordering metric
393 - KEEP_THE_SMALLER(t->info.tcp.tcpi_reordering, n->info.tcp.tcpi_reordering);
2689 + topology_write_response_metadata(wb);
2690 + topology_write_presentation(wb);
2691
395 - // The receive round trip time in milliseconds.
396 - KEEP_THE_BIGGER(t->info.tcp.tcpi_rcv_rtt, n->info.tcp.tcpi_rcv_rtt);
2692 + NV_TOPOLOGY_CONTEXT ctx;
2693 + bool ctx_ready = topology_prepare_context(&ctx, now_ut, &options);
2694 + if(ctx_ready)
2695 + topology_write_data(wb, &ctx);
2696
398 - // The available space in the receive buffer.
399 - KEEP_THE_SMALLER(t->info.tcp.tcpi_rcv_space, n->info.tcp.tcpi_rcv_space);
400 - }
401 - else {
402 - t = mallocz(sizeof(*t));
403 - memcpy(t, n, sizeof(*t));
404 - t->cmdline = string_dup(t->cmdline);
405 - simple_hashtable_set_slot_AGGREGATED_SOCKETS(ht, sl, hash, t);
406 - }
2697 + topology_context_destroy(&ctx);
2698 + topology_finalize_response(transaction, wb, now_s);
2699 }
2700
2701 static int local_sockets_compar(const void *a, const void *b) {
@@ -429,7 +2721,7 @@ void network_viewer_function(const char *transaction, char *function __maybe_unu
2721
2722 buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK);
2723 buffer_json_member_add_string(wb, "type", "table");
432 - buffer_json_member_add_time_t(wb, "update_every", 5);
2724 + buffer_json_member_add_time_t(wb, "update_every", NETWORK_VIEWER_RESPONSE_UPDATE_EVERY);
2725 buffer_json_member_add_boolean(wb, "has_history", false);
2726 buffer_json_member_add_string(wb, "help", NETWORK_CONNECTIONS_VIEWER_HELP);
2727
@@ -475,6 +2767,7 @@ void network_viewer_function(const char *transaction, char *function __maybe_unu
2767 size_t num_words = quoted_strings_splitter_whitespace(function_copy, words, 1024);
2768 for(size_t i = 1; i < num_words ;i++) {
2769 char *param = get_word(words, num_words, i);
2770 + if(!param || !*param) continue;
2771 if(strcmp(param, "sockets:aggregated") == 0) {
2772 aggregated = true;
2773 }
@@ -545,24 +2838,29 @@ void network_viewer_function(const char *transaction, char *function __maybe_unu
2838 local_sockets_process(&ls);
2839
2840 if(aggregated) {
548 - LOCAL_SOCKET *array[ht.used];
2841 size_t added = 0;
2842 uint64_t proc_self_net_ns_inode = ls.proc_self_net_ns_inode;
551 - for(SIMPLE_HASHTABLE_SLOT_AGGREGATED_SOCKETS *sl = simple_hashtable_first_read_only_AGGREGATED_SOCKETS(&ht);
552 - sl;
553 - sl = simple_hashtable_next_read_only_AGGREGATED_SOCKETS(&ht, sl)) {
554 - LOCAL_SOCKET *n = SIMPLE_HASHTABLE_SLOT_DATA(sl);
555 - if(!n || added >= ht.used) continue;
2843
557 - array[added++] = n;
558 - }
2844 + if(ht.used) {
2845 + LOCAL_SOCKET **array = mallocz(ht.used * sizeof(LOCAL_SOCKET *));
2846 + for(SIMPLE_HASHTABLE_SLOT_AGGREGATED_SOCKETS *sl = simple_hashtable_first_read_only_AGGREGATED_SOCKETS(&ht);
2847 + sl;
2848 + sl = simple_hashtable_next_read_only_AGGREGATED_SOCKETS(&ht, sl)) {
2849 + LOCAL_SOCKET *n = SIMPLE_HASHTABLE_SLOT_DATA(sl);
2850 + if(!n || added >= ht.used) continue;
2851
560 - qsort(array, added, sizeof(LOCAL_SOCKET *), local_sockets_compar);
2852 + array[added++] = n;
2853 + }
2854 +
2855 + qsort(array, added, sizeof(LOCAL_SOCKET *), local_sockets_compar);
2856 +
2857 + for(size_t i = 0; i < added ;i++) {
2858 + local_socket_to_json_array(&st, array[i], proc_self_net_ns_inode, true);
2859 + string_freez(array[i]->cmdline);
2860 + freez(array[i]);
2861 + }
2862
562 - for(size_t i = 0; i < added ;i++) {
563 - local_socket_to_json_array(&st, array[i], proc_self_net_ns_inode, true);
564 - string_freez(array[i]->cmdline);
565 - freez(array[i]);
2863 + freez(array);
2864 }
2865
2866 simple_hashtable_destroy_AGGREGATED_SOCKETS(&ht);
@@ -806,8 +3104,7 @@ void network_viewer_function(const char *transaction, char *function __maybe_unu
3104
3105 buffer_json_member_add_object(wb, "charts");
3106 {
809 - // Data Collection Age chart
810 - buffer_json_member_add_object(wb, "Count");
3107 + buffer_json_member_add_object(wb, "Count by Direction");
3108 {
3109 buffer_json_member_add_string(wb, "type", "stacked-bar");
3110 buffer_json_member_add_array(wb, "columns");
@@ -818,8 +3115,7 @@ void network_viewer_function(const char *transaction, char *function __maybe_unu
3115 }
3116 buffer_json_object_close(wb);
3117
821 - // Streaming Age chart
822 - buffer_json_member_add_object(wb, "Count");
3118 + buffer_json_member_add_object(wb, "Count by Process");
3119 {
3120 buffer_json_member_add_string(wb, "type", "stacked-bar");
3121 buffer_json_member_add_array(wb, "columns");
@@ -830,8 +3126,7 @@ void network_viewer_function(const char *transaction, char *function __maybe_unu
3126 }
3127 buffer_json_object_close(wb);
3128
833 - // DB Duration
834 - buffer_json_member_add_object(wb, "Count");
3129 + buffer_json_member_add_object(wb, "Count by Protocol");
3130 {
3131 buffer_json_member_add_string(wb, "type", "stacked-bar");
3132 buffer_json_member_add_array(wb, "columns");
@@ -954,13 +3249,13 @@ void network_viewer_function(const char *transaction, char *function __maybe_unu
3249 }
3250
3251 close_and_send:
957 - buffer_json_member_add_time_t(wb, "expires", now_s + 1);
3252 + buffer_json_member_add_time_t(wb, "expires", now_s + NETWORK_VIEWER_RESPONSE_UPDATE_EVERY);
3253 buffer_json_finalize(wb);
3254
3255 netdata_mutex_lock(&stdout_mutex);
3256 wb->response_code = HTTP_RESP_OK;
3257 wb->content_type = CT_APPLICATION_JSON;
963 - wb->expires = now_s + 1;
3258 + wb->expires = now_s + NETWORK_VIEWER_RESPONSE_UPDATE_EVERY;
3259 pluginsd_function_result_to_stdout(transaction, wb);
3260 netdata_mutex_unlock(&stdout_mutex);
3261 }
@@ -990,10 +3285,15 @@ int main(int argc __maybe_unused, char **argv __maybe_unused) {
3285
3286 // ----------------------------------------------------------------------------------------------------------------
3287
3288 + // Manual debug mode only; normal plugins.d execution never takes this path.
3289 if(argc == 2 && strcmp(argv[1], "debug") == 0) {
3290 // for(int i = 0; i < 100; i++) {
3291 bool cancelled = false;
3292 usec_t stop_monotonic_ut = now_monotonic_usec() + 600 * USEC_PER_SEC;
3293 + char topo_buf[] = "topology:network-connections";
3294 + network_viewer_topology_function("123", topo_buf, &stop_monotonic_ut, &cancelled,
3295 + NULL, HTTP_ACCESS_ALL, NULL, NULL);
3296 +
3297 char buf[] = "network-connections sockets:aggregated";
3298 network_viewer_function("123", buf, &stop_monotonic_ut, &cancelled,
3299 NULL, HTTP_ACCESS_ALL, NULL, NULL);
@@ -1009,6 +3309,12 @@ int main(int argc __maybe_unused, char **argv __maybe_unused) {
3309
3310 // ----------------------------------------------------------------------------------------------------------------
3311
3312 + fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\" \"top\" "HTTP_ACCESS_FORMAT" %d\n",
3313 + NETWORK_TOPOLOGY_VIEWER_FUNCTION, 60,
3314 + NETWORK_TOPOLOGY_VIEWER_HELP,
3315 + (HTTP_ACCESS_FORMAT_CAST)(HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA),
3316 + RRDFUNCTIONS_PRIORITY_DEFAULT);
3317 +
3318 fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\" \"top\" "HTTP_ACCESS_FORMAT" %d\n",
3319 NETWORK_CONNECTIONS_VIEWER_FUNCTION, 60,
3320 NETWORK_CONNECTIONS_VIEWER_HELP,
@@ -1025,6 +3331,11 @@ int main(int argc __maybe_unused, char **argv __maybe_unused) {
3331 PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT,
3332 NULL);
3333
3334 + functions_evloop_add_function(wg, NETWORK_TOPOLOGY_VIEWER_FUNCTION,
3335 + network_viewer_topology_function,
3336 + PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT,
3337 + NULL);
3338 +
3339 // ----------------------------------------------------------------------------------------------------------------
3340
3341 usec_t send_newline_ut = 0;
src/database/rrdhost-system-info.c
+31
@@ -625,6 +625,37 @@ void rrdhost_system_info_to_node_info(struct rrdhost_system_info *system_info, s
625 node_info->data.ml_info.ml_enabled = system_info->ml_enabled;
626 }
627
628 +// emit system info as named key-value pairs into an open JSON object
629 +void rrdhost_system_info_to_json_object_fields(BUFFER *wb, struct rrdhost_system_info *si) {
630 + buffer_json_member_add_string(wb, "os_name", si && si->host_os_name ? si->host_os_name : "");
631 + buffer_json_member_add_string(wb, "os_id", si && si->host_os_id ? si->host_os_id : "");
632 + buffer_json_member_add_string(wb, "os_id_like", si && si->host_os_id_like ? si->host_os_id_like : "");
633 + buffer_json_member_add_string(wb, "os_version", si && si->host_os_version ? si->host_os_version : "");
634 + buffer_json_member_add_string(wb, "os_version_id", si && si->host_os_version_id ? si->host_os_version_id : "");
635 + buffer_json_member_add_string(wb, "os_detection", si && si->host_os_detection ? si->host_os_detection : "");
636 + buffer_json_member_add_uint64(wb, "cpu_cores", si && si->host_cores ? str2uint64_t(si->host_cores, NULL) : 0);
637 + buffer_json_member_add_uint64(wb, "disk_space", si && si->host_disk_space ? str2uint64_t(si->host_disk_space, NULL) : 0);
638 + buffer_json_member_add_uint64(wb, "cpu_freq", si && si->host_cpu_freq ? str2uint64_t(si->host_cpu_freq, NULL) : 0);
639 + buffer_json_member_add_uint64(wb, "ram_total", si && si->host_ram_total ? str2uint64_t(si->host_ram_total, NULL) : 0);
640 + buffer_json_member_add_string(wb, "container_os_name", si && si->container_os_name ? si->container_os_name : "");
641 + buffer_json_member_add_string(wb, "container_os_id", si && si->container_os_id ? si->container_os_id : "");
642 + buffer_json_member_add_string(wb, "container_os_id_like", si && si->container_os_id_like ? si->container_os_id_like : "");
643 + buffer_json_member_add_string(wb, "container_os_version", si && si->container_os_version ? si->container_os_version : "");
644 + buffer_json_member_add_string(wb, "container_os_version_id",si && si->container_os_version_id ? si->container_os_version_id : "");
645 + buffer_json_member_add_string(wb, "container_os_detection", si && si->container_os_detection ? si->container_os_detection : "");
646 + buffer_json_member_add_string(wb, "is_k8s_node", si && si->is_k8s_node ? si->is_k8s_node : "");
647 + buffer_json_member_add_string(wb, "kernel_name", si && si->kernel_name ? si->kernel_name : "");
648 + buffer_json_member_add_string(wb, "kernel_version", si && si->kernel_version ? si->kernel_version : "");
649 + buffer_json_member_add_string(wb, "architecture", si && si->architecture ? si->architecture : "");
650 + buffer_json_member_add_string(wb, "virtualization", si && si->virtualization ? si->virtualization : "");
651 + buffer_json_member_add_string(wb, "virt_detection", si && si->virt_detection ? si->virt_detection : "");
652 + buffer_json_member_add_string(wb, "container_type", si && si->container ? si->container : "");
653 + buffer_json_member_add_string(wb, "container_detection", si && si->container_detection ? si->container_detection : "");
654 + buffer_json_member_add_string(wb, "cloud_provider_type", si && si->cloud_provider_type ? si->cloud_provider_type : "");
655 + buffer_json_member_add_string(wb, "cloud_instance_type", si && si->cloud_instance_type ? si->cloud_instance_type : "");
656 + buffer_json_member_add_string(wb, "cloud_instance_region", si && si->cloud_instance_region ? si->cloud_instance_region : "");
657 +}
658 +
659 void rrdhost_system_info_to_streaming_function_array(BUFFER *wb, struct rrdhost_system_info *system_info) {
660 if(system_info) {
661 buffer_json_add_array_item_string(wb, system_info->host_os_name ? system_info->host_os_name : "");
src/database/rrdhost-system-info.h
+1
@@ -104,6 +104,7 @@ struct update_node_info;
104 void rrdhost_system_info_to_node_info(struct rrdhost_system_info *system_info, struct update_node_info *node_info);
105
106 void rrdhost_system_info_to_streaming_function_array(BUFFER *wb, struct rrdhost_system_info *system_info);
107 +void rrdhost_system_info_to_json_object_fields(BUFFER *wb, struct rrdhost_system_info *system_info);
108
109 bool get_daemon_status_fields_from_system_info(DAEMON_STATUS_FILE *ds);
110 void rrdhost_system_info_swap(struct rrdhost_system_info *a, struct rrdhost_system_info *b);
src/libnetdata/local-sockets/local-sockets.h
+38 -10
@@ -193,6 +193,7 @@ typedef enum __attribute__((packed)) {
193 struct pid_socket {
194 uint64_t inode;
195 pid_t pid;
196 + pid_t ppid;
197 uid_t uid;
198 uint64_t net_ns_inode;
199 char *cmdline;
@@ -236,6 +237,7 @@ typedef struct local_socket {
237 struct socket_endpoint local;
238 struct socket_endpoint remote;
239 pid_t pid;
240 + pid_t ppid;
241
242 SOCKET_DIRECTION direction;
243
@@ -484,7 +486,7 @@ static inline void local_listeners_print_socket(LS_STATE *ls __maybe_unused, con
486 ipv6_address_to_txt(&n->remote.ip.ipv6, remote_address);
487 }
488
487 - printf("%s, direction=%s%s%s%s%s pid=%d, state=0x%0x, ns=%"PRIu64", local=%s[:%u], remote=%s[:%u], uid=%u, inode=%"PRIu64", comm=%s\n",
489 + printf("%s, direction=%s%s%s%s%s pid=%d, ppid=%d, state=0x%0x, ns=%"PRIu64", local=%s[:%u], remote=%s[:%u], uid=%u, inode=%"PRIu64", comm=%s\n",
490 local_sockets_protocol_name(n),
491 (n->direction & SOCKET_DIRECTION_LISTEN) ? "LISTEN," : "",
492 (n->direction & SOCKET_DIRECTION_INBOUND) ? "INBOUND," : "",
@@ -492,6 +494,7 @@ static inline void local_listeners_print_socket(LS_STATE *ls __maybe_unused, con
494 (n->direction & (SOCKET_DIRECTION_LOCAL_INBOUND|SOCKET_DIRECTION_LOCAL_OUTBOUND)) ? "LOCAL," : "",
495 (n->direction == 0) ? "NONE," : "",
496 n->pid,
497 + n->ppid,
498 (unsigned int)n->state,
499 n->net_ns_inode,
500 local_address, n->local.port,
@@ -619,6 +622,10 @@ static inline bool local_sockets_find_all_sockets_in_proc(LS_STATE *ls, const ch
622 closedir(fd_dir);
623 continue;
624 }
625 + pid_t ppid = 0;
626 + bool ppid_checked = false;
627 + bool status_checked = false;
628 + bool status_failed = false;
629 net_ns_inode = 0;
630 uid_t uid = UID_UNSET;
631
@@ -637,19 +644,38 @@ static inline bool local_sockets_find_all_sockets_in_proc(LS_STATE *ls, const ch
644 SIMPLE_HASHTABLE_SLOT_PID_SOCKET *sl = simple_hashtable_get_slot_PID_SOCKET(&ls->pid_sockets_hashtable, inode_hash, &inode, true);
645 struct pid_socket *ps = SIMPLE_HASHTABLE_SLOT_DATA(sl);
646 if(!ps || (ps->pid == 1 && pid != 1)) {
640 - if(uid == UID_UNSET && ls->config.uid) {
647 + if(!status_checked && !status_failed &&
648 + ((uid == UID_UNSET && ls->config.uid) || (!ppid_checked && ls->config.pid))) {
649 char status_buf[512];
650 +
651 snprintfz(filename, sizeof(filename), "%s/%s/status", proc_filename, proc_entry->d_name);
643 - if (read_txt_file(filename, status_buf, sizeof(status_buf)))
652 + if (read_txt_file(filename, status_buf, sizeof(status_buf))) {
653 + status_failed = true;
654 local_sockets_log(ls, "cannot open file: %s\n", filename);
655 + }
656 else {
646 - char *u = strstr(status_buf, "Uid:");
647 - if(u) {
648 - u += 4;
649 - while(isspace(*u)) u++; // skip spaces
650 - while(*u >= '0' && *u <= '9') u++; // skip the first number (real uid)
651 - while(isspace(*u)) u++; // skip spaces again
652 - uid = strtol(u, NULL, 10); // parse the 2nd number (effective uid)
657 + status_checked = true;
658 + if(ls->config.pid)
659 + ppid_checked = true;
660 +
661 + if(ls->config.uid) {
662 + char *u = strstr(status_buf, "Uid:");
663 + if(u) {
664 + u += 4;
665 + while(isspace((unsigned char)*u)) u++; // skip spaces
666 + while(*u >= '0' && *u <= '9') u++; // skip the first number (real uid)
667 + while(isspace((unsigned char)*u)) u++; // skip spaces again
668 + uid = strtol(u, NULL, 10); // parse the 2nd number (effective uid)
669 + }
670 + }
671 +
672 + if(ls->config.pid) {
673 + char *p = strstr(status_buf, "PPid:");
674 + if(p) {
675 + p += 5;
676 + while(isspace((unsigned char)*p)) p++; // skip spaces
677 + ppid = (pid_t)strtol(p, NULL, 10); // parse parent pid
678 + }
679 }
680 }
681 }
@@ -686,6 +712,7 @@ static inline bool local_sockets_find_all_sockets_in_proc(LS_STATE *ls, const ch
712
713 ps->inode = inode;
714 ps->pid = pid;
715 + ps->ppid = ppid;
716 ps->uid = uid;
717 ps->net_ns_inode = net_ns_inode;
718 strncpyz(ps->comm, comm, sizeof(ps->comm) - 1);
@@ -755,6 +782,7 @@ static inline bool local_sockets_add_socket(LS_STATE *ls, LOCAL_SOCKET *tmp) {
782 if(ps) {
783 n->net_ns_inode = ps->net_ns_inode;
784 n->pid = ps->pid;
785 + n->ppid = ps->ppid;
786
787 if(ps->uid != UID_UNSET && n->uid == UID_UNSET)
788 n->uid = ps->uid;
src/streaming/stream-path.c
+12
@@ -246,6 +246,18 @@ void stream_path_send_to_child(RRDHOST *host) {
246 rrdhost_receiver_unlock(host);
247 }
248
249 +uint16_t rrdhost_stream_path_get_host_ids(struct rrdhost *host, uint16_t from, ND_UUID *host_ids, uint16_t max) {
250 + if(!host || !host_ids || !max)
251 + return 0;
252 +
253 + uint16_t count = 0;
254 + rw_spinlock_read_lock(&host->stream.path.spinlock);
255 + for(uint16_t i = from; i < host->stream.path.used && count < max; i++)
256 + host_ids[count++] = host->stream.path.array[i].host_id;
257 + rw_spinlock_read_unlock(&host->stream.path.spinlock);
258 + return count;
259 +}
260 +
261 void stream_path_child_disconnected(RRDHOST *host) {
262 rrdhost_stream_path_clear(host, true);
263 }
src/streaming/stream-path.h
+4
@@ -38,4 +38,8 @@ bool stream_path_set_from_json(struct rrdhost *host, const char *json, bool from
38
39 bool rrdhost_is_host_in_stream_path_before_us(struct rrdhost *host, ND_UUID remote_agent_host_id, int16_t our_hops);
40
41 +// copy host_ids from the streaming path, starting at position 'from', into host_ids array
42 +// returns the number of entries copied (up to max)
43 +uint16_t rrdhost_stream_path_get_host_ids(struct rrdhost *host, uint16_t from, ND_UUID *host_ids, uint16_t max);
44 +
45 #endif //NETDATA_STREAM_PATH_H
src/web/api/functions/function-streaming.c
+1479 -2
@@ -2,6 +2,10 @@
2
3 #include "function-streaming.h"
4
5 +#include <ctype.h>
6 +
7 +#define STREAMING_FUNCTION_UPDATE_EVERY 10
8 +
9 #define GROUP_BY_COLUMN(name, descr) \
10 buffer_json_member_add_object(wb, name);\
11 {\
@@ -14,6 +18,1479 @@
18 }\
19 buffer_json_object_close(wb);
20
21 +static bool streaming_topology_host_guid(RRDHOST *host, char *dst, size_t dst_size);
22 +static bool streaming_topology_uuid_guid(ND_UUID host_id, char *dst, size_t dst_size);
23 +static void streaming_topology_agent_id_for_host(RRDHOST *host, char *dst, size_t dst_size);
24 +static void streaming_topology_actor_id_from_guid(const char *guid, char *dst, size_t dst_size);
25 +static void streaming_topology_actor_id_for_uuid(ND_UUID host_id, char *dst, size_t dst_size);
26 +static uint32_t *streaming_topology_parent_child_count_get(DICTIONARY *parent_child_count, RRDHOST *host);
27 +static struct streaming_topology_descendant_list *streaming_topology_descendants_get(DICTIONARY *parent_descendants, RRDHOST *host);
28 +
29 +enum streaming_topology_received_type {
30 + STREAMING_TOPOLOGY_RECEIVED_STREAMING = 0,
31 + STREAMING_TOPOLOGY_RECEIVED_VIRTUAL,
32 + STREAMING_TOPOLOGY_RECEIVED_STALE,
33 +};
34 +
35 +struct streaming_topology_descendant {
36 + RRDHOST *host;
37 + ND_UUID source_uuid;
38 + bool source_local;
39 + uint8_t type;
40 +};
41 +
42 +struct streaming_topology_descendant_list {
43 + struct streaming_topology_descendant *items;
44 + size_t used;
45 + size_t size;
46 +};
47 +
48 +struct streaming_topology_filters {
49 + bool info_only;
50 + const char *node_type;
51 + const char *ingest_status;
52 + const char *stream_status;
53 + char *function_copy;
54 +};
55 +
56 +static void streaming_topology_add_host_match(BUFFER *wb, RRDHOST *host) {
57 + buffer_json_member_add_object(wb, "match");
58 + {
59 + buffer_json_member_add_array(wb, "hostnames");
60 + {
61 + buffer_json_add_array_item_string(wb, rrdhost_hostname(host));
62 + }
63 + buffer_json_array_close(wb);
64 +
65 + char host_guid[UUID_STR_LEN];
66 + if(streaming_topology_host_guid(host, host_guid, sizeof(host_guid)))
67 + buffer_json_member_add_string(wb, "netdata_machine_guid", host_guid);
68 +
69 + if(!UUIDiszero(host->node_id))
70 + buffer_json_member_add_uuid(wb, "netdata_node_id", host->node_id.uuid);
71 + }
72 + buffer_json_object_close(wb);
73 +}
74 +
75 +static bool streaming_topology_host_guid(RRDHOST *host, char *dst, size_t dst_size) {
76 + if(!dst || dst_size < UUID_STR_LEN)
77 + return false;
78 +
79 + dst[0] = '\0';
80 + if(!host)
81 + return false;
82 +
83 + if(streaming_topology_uuid_guid(host->host_id, dst, dst_size))
84 + return true;
85 +
86 + if(host->machine_guid[0]) {
87 + ND_UUID machine_guid = UUID_ZERO;
88 + if(!uuid_parse(host->machine_guid, machine_guid.uuid))
89 + return streaming_topology_uuid_guid(machine_guid, dst, dst_size);
90 + }
91 +
92 + return false;
93 +}
94 +
95 +static bool streaming_topology_uuid_guid(ND_UUID host_id, char *dst, size_t dst_size) {
96 + if(!dst || dst_size < UUID_STR_LEN)
97 + return false;
98 +
99 + dst[0] = '\0';
100 + if(UUIDiszero(host_id))
101 + return false;
102 +
103 + uuid_unparse_lower(host_id.uuid, dst);
104 + return true;
105 +}
106 +
107 +static void streaming_topology_actor_id_from_guid(const char *guid, char *dst, size_t dst_size) {
108 + if(!dst || !dst_size)
109 + return;
110 +
111 + if(guid && *guid)
112 + snprintf(dst, dst_size, "netdata-machine-guid:%s", guid);
113 + else
114 + snprintf(dst, dst_size, "host:unknown");
115 +}
116 +
117 +static void streaming_topology_agent_id_for_host(RRDHOST *host, char *dst, size_t dst_size) {
118 + if(!dst || !dst_size)
119 + return;
120 +
121 + char host_guid[UUID_STR_LEN];
122 + if(streaming_topology_host_guid(host, host_guid, sizeof(host_guid)))
123 + snprintf(dst, dst_size, "%s", host_guid);
124 + else if(host)
125 + snprintf(dst, dst_size, "%s", rrdhost_hostname(host));
126 + else
127 + dst[0] = '\0';
128 +}
129 +
130 +static void streaming_topology_actor_id_for_uuid(ND_UUID host_id, char *dst, size_t dst_size) {
131 + char guid[UUID_STR_LEN];
132 + if(streaming_topology_uuid_guid(host_id, guid, sizeof(guid)))
133 + streaming_topology_actor_id_from_guid(guid, dst, dst_size);
134 + else
135 + streaming_topology_actor_id_from_guid(NULL, dst, dst_size);
136 +}
137 +
138 +static uint32_t *streaming_topology_parent_child_count_get(DICTIONARY *parent_child_count, RRDHOST *host) {
139 + char host_guid[UUID_STR_LEN];
140 +
141 + if(!streaming_topology_host_guid(host, host_guid, sizeof(host_guid)))
142 + return NULL;
143 +
144 + return dictionary_get(parent_child_count, host_guid);
145 +}
146 +
147 +static struct streaming_topology_descendant_list *streaming_topology_descendants_get(DICTIONARY *parent_descendants, RRDHOST *host) {
148 + char host_guid[UUID_STR_LEN];
149 +
150 + if(!streaming_topology_host_guid(host, host_guid, sizeof(host_guid)))
151 + return NULL;
152 +
153 + return dictionary_get(parent_descendants, host_guid);
154 +}
155 +
156 +static struct streaming_topology_descendant_list *streaming_topology_descendants_get_or_create(DICTIONARY *parent_descendants, ND_UUID host_id) {
157 + char host_guid[UUID_STR_LEN];
158 + if(!streaming_topology_uuid_guid(host_id, host_guid, sizeof(host_guid)))
159 + return NULL;
160 +
161 + struct streaming_topology_descendant_list *list = dictionary_get(parent_descendants, host_guid);
162 + if(list)
163 + return list;
164 +
165 + struct streaming_topology_descendant_list empty = {0};
166 + return dictionary_set(parent_descendants, host_guid, &empty, sizeof(empty));
167 +}
168 +
169 +static void streaming_topology_descendants_append(
170 + DICTIONARY *parent_descendants,
171 + ND_UUID parent_id,
172 + RRDHOST *host,
173 + enum streaming_topology_received_type type,
174 + bool source_local,
175 + ND_UUID source_uuid
176 +) {
177 + struct streaming_topology_descendant_list *list = streaming_topology_descendants_get_or_create(parent_descendants, parent_id);
178 + if(!list)
179 + return;
180 +
181 + if(list->used == list->size) {
182 + size_t new_size = list->size ? list->size * 2 : 4;
183 + list->items = reallocz(list->items, new_size * sizeof(*list->items));
184 + list->size = new_size;
185 + }
186 +
187 + list->items[list->used++] = (struct streaming_topology_descendant) {
188 + .host = host,
189 + .source_uuid = source_uuid,
190 + .source_local = source_local,
191 + .type = (uint8_t)type,
192 + };
193 +}
194 +
195 +static const char *streaming_topology_received_type_to_string(enum streaming_topology_received_type type) {
196 + switch(type) {
197 + case STREAMING_TOPOLOGY_RECEIVED_VIRTUAL:
198 + return "virtual";
199 +
200 + case STREAMING_TOPOLOGY_RECEIVED_STALE:
201 + return "stale";
202 +
203 + case STREAMING_TOPOLOGY_RECEIVED_STREAMING:
204 + default:
205 + return "streaming";
206 + }
207 +}
208 +
209 +static void streaming_topology_actor_id_for_host(RRDHOST *host, char *dst, size_t dst_size) {
210 + if(!dst || !dst_size)
211 + return;
212 +
213 + char host_guid[UUID_STR_LEN];
214 + if(streaming_topology_host_guid(host, host_guid, sizeof(host_guid)))
215 + streaming_topology_actor_id_from_guid(host_guid, dst, dst_size);
216 + else if(host)
217 + snprintf(dst, dst_size, "hostname:%s", rrdhost_hostname(host));
218 + else
219 + snprintf(dst, dst_size, "host:unknown");
220 +}
221 +
222 +// get streaming_path host_ids, appending localhost only when the path already
223 +// has upstream entries but does not yet include us; callers still use n == 0
224 +// to detect hosts without an active path
225 +static uint16_t streaming_topology_get_path_ids(RRDHOST *host, uint16_t from, ND_UUID *host_ids, uint16_t max) {
226 + uint16_t n = rrdhost_stream_path_get_host_ids(host, from, host_ids, max);
227 + uint16_t filtered_n = 0;
228 +
229 + // check if localhost is already in the path
230 + bool found_localhost = false;
231 + for(uint16_t i = 0; i < n; i++) {
232 + if(UUIDiszero(host_ids[i]))
233 + continue;
234 +
235 + host_ids[filtered_n++] = host_ids[i];
236 + if(UUIDeq(host_ids[i], localhost->host_id)) {
237 + found_localhost = true;
238 + }
239 + }
240 + n = filtered_n;
241 +
242 + // append localhost if not found (same as rrdhost_stream_path_to_json)
243 + if(!found_localhost && n < max && n > 0)
244 + host_ids[n++] = localhost->host_id;
245 +
246 + return n;
247 +}
248 +
249 +static void streaming_topology_parse_filters(const char *function, struct streaming_topology_filters *filters) {
250 + if(!filters)
251 + return;
252 +
253 + *filters = (struct streaming_topology_filters){ 0 };
254 + if(!function || !*function)
255 + return;
256 +
257 + filters->function_copy = strdupz(function);
258 + char *words[1024];
259 + size_t num_words = quoted_strings_splitter_whitespace(filters->function_copy, words, 1024);
260 + for(size_t i = 1; i < num_words; i++) {
261 + char *param = get_word(words, num_words, i);
262 + if(strcmp(param, "info") == 0)
263 + filters->info_only = true;
264 + else if(strncmp(param, "node_type:", 10) == 0)
265 + filters->node_type = param + 10;
266 + else if(strncmp(param, "ingest_status:", 14) == 0)
267 + filters->ingest_status = param + 14;
268 + else if(strncmp(param, "stream_status:", 14) == 0)
269 + filters->stream_status = param + 14;
270 + }
271 +}
272 +
273 +static int streaming_topology_return_error(BUFFER *wb, char *function_copy, int status, const char *error) {
274 + buffer_flush(wb);
275 + wb->content_type = CT_APPLICATION_JSON;
276 + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
277 +
278 + buffer_json_member_add_uint64(wb, "status", status);
279 + buffer_json_member_add_string(wb, "type", "topology");
280 + buffer_json_member_add_time_t(wb, "update_every", STREAMING_FUNCTION_UPDATE_EVERY);
281 + buffer_json_member_add_boolean(wb, "has_history", false);
282 + buffer_json_member_add_string(wb, "help", RRDFUNCTIONS_STREAMING_TOPOLOGY_HELP);
283 + buffer_json_member_add_string(wb, "error", error);
284 + buffer_json_finalize(wb);
285 +
286 + freez(function_copy);
287 + return status;
288 +}
289 +
290 +// check if a value appears in a comma-separated list (NULL list matches everything)
291 +static bool value_in_csv(const char *csv, const char *value) {
292 + if(!csv || !*csv) return true;
293 + if(!value || !*value) return false;
294 + size_t vlen = strlen(value);
295 + const char *p = csv;
296 + while(*p) {
297 + const char *comma = strchr(p, ',');
298 + size_t len = comma ? (size_t)(comma - p) : strlen(p);
299 + while(len && isspace((unsigned char)*p)) {
300 + p++;
301 + len--;
302 + }
303 + while(len && isspace((unsigned char)p[len - 1]))
304 + len--;
305 + if(len == vlen && strncmp(p, value, len) == 0)
306 + return true;
307 + if(!comma) break;
308 + p = comma + 1;
309 + }
310 + return false;
311 +}
312 +
313 +// helpers: emit one summary field and one table column
314 +static void streaming_topology_emit_sf(BUFFER *wb, const char *key, const char *label, const char *source) {
315 + buffer_json_add_array_item_object(wb);
316 + buffer_json_member_add_string(wb, "key", key);
317 + buffer_json_member_add_string(wb, "label", label);
318 + buffer_json_member_add_array(wb, "sources");
319 + buffer_json_add_array_item_string(wb, source);
320 + buffer_json_array_close(wb);
321 + buffer_json_object_close(wb);
322 +}
323 +
324 +static void streaming_topology_emit_col(BUFFER *wb, const char *key, const char *label, const char *type) {
325 + buffer_json_add_array_item_object(wb);
326 + buffer_json_member_add_string(wb, "key", key);
327 + buffer_json_member_add_string(wb, "label", label);
328 + if(type)
329 + buffer_json_member_add_string(wb, "type", type);
330 + buffer_json_object_close(wb);
331 +}
332 +
333 +static void streaming_topology_info_tab(BUFFER *wb) {
334 + buffer_json_member_add_array(wb, "modal_tabs");
335 + {
336 + buffer_json_add_array_item_object(wb);
337 + buffer_json_member_add_string(wb, "id", "info");
338 + buffer_json_member_add_string(wb, "label", "Info");
339 + buffer_json_object_close(wb);
340 + }
341 + buffer_json_array_close(wb);
342 +}
343 +
344 +// streaming path table: shared by all non-parent types
345 +static void streaming_topology_emit_streaming_path_table(BUFFER *wb, uint64_t order) {
346 + buffer_json_member_add_object(wb, "streaming_path");
347 + {
348 + buffer_json_member_add_string(wb, "label", "Streaming Path");
349 + buffer_json_member_add_string(wb, "source", "data");
350 + buffer_json_member_add_uint64(wb, "order", order);
351 + buffer_json_member_add_array(wb, "columns");
352 + {
353 + streaming_topology_emit_col(wb, "hostname", "Agent", NULL);
354 + streaming_topology_emit_col(wb, "hops", "Hops", "number");
355 + streaming_topology_emit_col(wb, "since", "Since", "number");
356 + streaming_topology_emit_col(wb, "flags", "Flags", NULL);
357 + }
358 + buffer_json_array_close(wb);
359 + }
360 + buffer_json_object_close(wb);
361 +}
362 +
363 +// retention table: shared by all types
364 +static void streaming_topology_emit_retention_table(BUFFER *wb, uint64_t order, const char *name_label) {
365 + buffer_json_member_add_object(wb, "retention");
366 + {
367 + buffer_json_member_add_string(wb, "label", "Retention");
368 + buffer_json_member_add_string(wb, "source", "data");
369 + buffer_json_member_add_uint64(wb, "order", order);
370 + buffer_json_member_add_array(wb, "columns");
371 + {
372 + streaming_topology_emit_col(wb, "name", name_label, "actor_link");
373 + streaming_topology_emit_col(wb, "db_status", "Status", "badge");
374 + streaming_topology_emit_col(wb, "db_from", "From", "timestamp");
375 + streaming_topology_emit_col(wb, "db_to", "To", "timestamp");
376 + streaming_topology_emit_col(wb, "db_duration", "Duration", "duration");
377 + streaming_topology_emit_col(wb, "db_metrics", "Metrics", "number");
378 + streaming_topology_emit_col(wb, "db_instances", "Instances", "number");
379 + streaming_topology_emit_col(wb, "db_contexts", "Contexts", "number");
380 + }
381 + buffer_json_array_close(wb);
382 + }
383 + buffer_json_object_close(wb);
384 +}
385 +
386 +// parent actor presentation: intrinsic summary + inbound/retention tables
387 +static void streaming_topology_parent_presentation(BUFFER *wb) {
388 + buffer_json_member_add_array(wb, "summary_fields");
389 + {
390 + streaming_topology_emit_sf(wb, "display_name", "Name", "attributes.display_name");
391 + streaming_topology_emit_sf(wb, "node_type", "Type", "attributes.node_type");
392 + streaming_topology_emit_sf(wb, "agent_version", "Version", "attributes.agent_version");
393 + streaming_topology_emit_sf(wb, "os_name", "OS", "attributes.os_name");
394 + streaming_topology_emit_sf(wb, "architecture", "Arch", "attributes.architecture");
395 + streaming_topology_emit_sf(wb, "cpu_cores", "CPUs", "attributes.cpu_cores");
396 + streaming_topology_emit_sf(wb, "child_count", "Children", "attributes.child_count");
397 + streaming_topology_emit_sf(wb, "health_critical", "Alerts Critical", "attributes.health_critical");
398 + streaming_topology_emit_sf(wb, "health_warning", "Alerts Warning", "attributes.health_warning");
399 + }
400 + buffer_json_array_close(wb);
401 +
402 + buffer_json_member_add_object(wb, "tables");
403 + {
404 + // inbound: all nodes this parent has data for (rows = all known hosts)
405 + buffer_json_member_add_object(wb, "inbound");
406 + {
407 + buffer_json_member_add_string(wb, "label", "Inbound");
408 + buffer_json_member_add_string(wb, "source", "data");
409 + buffer_json_member_add_boolean(wb, "bullet_source", true);
410 + buffer_json_member_add_uint64(wb, "order", 0);
411 + buffer_json_member_add_array(wb, "columns");
412 + {
413 + streaming_topology_emit_col(wb, "name", "Node", "actor_link");
414 + streaming_topology_emit_col(wb, "received_from", "Source", "actor_link");
415 + streaming_topology_emit_col(wb, "node_type", "Type", "badge");
416 + streaming_topology_emit_col(wb, "ingest_status", "Ingest", "badge");
417 + streaming_topology_emit_col(wb, "hops", "Hops", "number");
418 + streaming_topology_emit_col(wb, "collected_metrics", "Metrics", "number");
419 + streaming_topology_emit_col(wb, "collected_instances", "Instances", "number");
420 + streaming_topology_emit_col(wb, "collected_contexts", "Contexts", "number");
421 + streaming_topology_emit_col(wb, "repl_completion", "Replication", "number");
422 + streaming_topology_emit_col(wb, "ingest_age", "Age", "duration");
423 + streaming_topology_emit_col(wb, "ssl", "SSL", "badge");
424 + streaming_topology_emit_col(wb, "alerts_critical", "Critical", "number");
425 + streaming_topology_emit_col(wb, "alerts_warning", "Warning", "number");
426 + }
427 + buffer_json_array_close(wb);
428 + }
429 + buffer_json_object_close(wb);
430 +
431 + // outbound: all nodes this parent streams to its parent (rows = all known hosts)
432 + buffer_json_member_add_object(wb, "outbound");
433 + {
434 + buffer_json_member_add_string(wb, "label", "Outbound");
435 + buffer_json_member_add_string(wb, "source", "data");
436 + buffer_json_member_add_uint64(wb, "order", 1);
437 + buffer_json_member_add_array(wb, "columns");
438 + {
439 + streaming_topology_emit_col(wb, "name", "Node", "actor_link");
440 + streaming_topology_emit_col(wb, "streamed_to", "Destination", "actor_link");
441 + streaming_topology_emit_col(wb, "node_type", "Type", "badge");
442 + streaming_topology_emit_col(wb, "stream_status", "Status", "badge");
443 + streaming_topology_emit_col(wb, "hops", "Hops", "number");
444 + streaming_topology_emit_col(wb, "ssl", "SSL", "badge");
445 + streaming_topology_emit_col(wb, "compression", "Compression", "badge");
446 + }
447 + buffer_json_array_close(wb);
448 + }
449 + buffer_json_object_close(wb);
450 +
451 + streaming_topology_emit_retention_table(wb, 2, "Node");
452 + streaming_topology_emit_streaming_path_table(wb, 3);
453 + }
454 + buffer_json_object_close(wb);
455 +
456 + streaming_topology_info_tab(wb);
457 +}
458 +
459 +// child actor presentation: intrinsic summary + streaming path/retention tables
460 +static void streaming_topology_child_presentation(BUFFER *wb) {
461 + buffer_json_member_add_array(wb, "summary_fields");
462 + {
463 + streaming_topology_emit_sf(wb, "display_name", "Name", "attributes.display_name");
464 + streaming_topology_emit_sf(wb, "node_type", "Type", "attributes.node_type");
465 + streaming_topology_emit_sf(wb, "agent_version", "Version", "attributes.agent_version");
466 + streaming_topology_emit_sf(wb, "os_name", "OS", "attributes.os_name");
467 + streaming_topology_emit_sf(wb, "architecture", "Arch", "attributes.architecture");
468 + streaming_topology_emit_sf(wb, "cpu_cores", "CPUs", "attributes.cpu_cores");
469 + streaming_topology_emit_sf(wb, "health_critical", "Alerts Critical", "attributes.health_critical");
470 + streaming_topology_emit_sf(wb, "health_warning", "Alerts Warning", "attributes.health_warning");
471 + }
472 + buffer_json_array_close(wb);
473 +
474 + buffer_json_member_add_object(wb, "tables");
475 + {
476 + streaming_topology_emit_streaming_path_table(wb, 0);
477 + streaming_topology_emit_retention_table(wb, 1, "Parent");
478 + }
479 + buffer_json_object_close(wb);
480 +
481 + streaming_topology_info_tab(wb);
482 +}
483 +
484 +// vnode actor presentation: minimal summary + streaming path/retention
485 +static void streaming_topology_vnode_presentation(BUFFER *wb) {
486 + buffer_json_member_add_array(wb, "summary_fields");
487 + {
488 + streaming_topology_emit_sf(wb, "display_name", "Name", "attributes.display_name");
489 + streaming_topology_emit_sf(wb, "node_type", "Type", "attributes.node_type");
490 + streaming_topology_emit_sf(wb, "ephemerality", "Ephemerality", "attributes.ephemerality");
491 + }
492 + buffer_json_array_close(wb);
493 +
494 + buffer_json_member_add_object(wb, "tables");
495 + {
496 + streaming_topology_emit_streaming_path_table(wb, 0);
497 + streaming_topology_emit_retention_table(wb, 1, "Parent");
498 + }
499 + buffer_json_object_close(wb);
500 +
501 + streaming_topology_info_tab(wb);
502 +}
503 +
504 +// stale actor presentation: identity summary + streaming path/retention
505 +static void streaming_topology_stale_presentation(BUFFER *wb) {
506 + buffer_json_member_add_array(wb, "summary_fields");
507 + {
508 + streaming_topology_emit_sf(wb, "display_name", "Name", "attributes.display_name");
509 + streaming_topology_emit_sf(wb, "node_type", "Type", "attributes.node_type");
510 + streaming_topology_emit_sf(wb, "agent_version", "Version", "attributes.agent_version");
511 + streaming_topology_emit_sf(wb, "os_name", "OS", "attributes.os_name");
512 + streaming_topology_emit_sf(wb, "architecture", "Arch", "attributes.architecture");
513 + }
514 + buffer_json_array_close(wb);
515 +
516 + buffer_json_member_add_object(wb, "tables");
517 + {
518 + streaming_topology_emit_streaming_path_table(wb, 0);
519 + streaming_topology_emit_retention_table(wb, 1, "Parent");
520 + }
521 + buffer_json_object_close(wb);
522 +
523 + streaming_topology_info_tab(wb);
524 +}
525 +
526 +int function_streaming_topology(BUFFER *wb, const char *function, BUFFER *payload __maybe_unused, const char *source __maybe_unused) {
527 + time_t now = now_realtime_sec();
528 + usec_t now_ut = now_realtime_usec();
529 +
530 + struct streaming_topology_filters filters = { 0 };
531 + streaming_topology_parse_filters(function, &filters);
532 + bool info_only = filters.info_only;
533 + const char *filter_node_type = filters.node_type;
534 + const char *filter_ingest_status = filters.ingest_status;
535 + const char *filter_stream_status = filters.stream_status;
536 + char *function_copy = filters.function_copy;
537 +
538 + buffer_flush(wb);
539 + wb->content_type = CT_APPLICATION_JSON;
540 + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
541 +
542 + buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK);
543 + buffer_json_member_add_string(wb, "type", "topology");
544 + buffer_json_member_add_time_t(wb, "update_every", STREAMING_FUNCTION_UPDATE_EVERY);
545 + buffer_json_member_add_boolean(wb, "has_history", false);
546 + buffer_json_member_add_string(wb, "help", RRDFUNCTIONS_STREAMING_TOPOLOGY_HELP);
547 + buffer_json_member_add_array(wb, "accepted_params");
548 + {
549 + buffer_json_add_array_item_string(wb, "node_type");
550 + buffer_json_add_array_item_string(wb, "ingest_status");
551 + buffer_json_add_array_item_string(wb, "stream_status");
552 + buffer_json_add_array_item_string(wb, "info");
553 + }
554 + buffer_json_array_close(wb);
555 + buffer_json_member_add_array(wb, "required_params");
556 + buffer_json_array_close(wb);
557 +
558 + // --- presentation metadata ---
559 + buffer_json_member_add_object(wb, "presentation");
560 + {
561 + buffer_json_member_add_object(wb, "actor_types");
562 + {
563 + buffer_json_member_add_object(wb, "parent");
564 + {
565 + buffer_json_member_add_string(wb, "label", "Netdata Parent");
566 + buffer_json_member_add_string(wb, "color_slot", "primary");
567 + buffer_json_member_add_double(wb, "opacity", 1.0);
568 + buffer_json_member_add_boolean(wb, "border", true);
569 + buffer_json_member_add_boolean(wb, "size_by_links", true);
570 + buffer_json_member_add_boolean(wb, "show_port_bullets", true);
571 + streaming_topology_parent_presentation(wb);
572 + }
573 + buffer_json_object_close(wb);
574 +
575 + buffer_json_member_add_object(wb, "child");
576 + {
577 + buffer_json_member_add_string(wb, "label", "Netdata Child");
578 + buffer_json_member_add_string(wb, "color_slot", "primary");
579 + buffer_json_member_add_double(wb, "opacity", 1.0);
580 + buffer_json_member_add_boolean(wb, "border", false);
581 + buffer_json_member_add_boolean(wb, "size_by_links", false);
582 + buffer_json_member_add_boolean(wb, "show_port_bullets", false);
583 + streaming_topology_child_presentation(wb);
584 + }
585 + buffer_json_object_close(wb);
586 +
587 + buffer_json_member_add_object(wb, "vnode");
588 + {
589 + buffer_json_member_add_string(wb, "label", "Virtual Node");
590 + buffer_json_member_add_string(wb, "color_slot", "warning");
591 + buffer_json_member_add_double(wb, "opacity", 1.0);
592 + buffer_json_member_add_boolean(wb, "border", false);
593 + buffer_json_member_add_boolean(wb, "size_by_links", false);
594 + buffer_json_member_add_boolean(wb, "show_port_bullets", false);
595 + streaming_topology_vnode_presentation(wb);
596 + }
597 + buffer_json_object_close(wb);
598 +
599 + buffer_json_member_add_object(wb, "stale");
600 + {
601 + buffer_json_member_add_string(wb, "label", "Stale Node");
602 + buffer_json_member_add_string(wb, "color_slot", "dim");
603 + buffer_json_member_add_double(wb, "opacity", 0.5);
604 + buffer_json_member_add_boolean(wb, "border", false);
605 + buffer_json_member_add_boolean(wb, "size_by_links", false);
606 + buffer_json_member_add_boolean(wb, "show_port_bullets", false);
607 + streaming_topology_stale_presentation(wb);
608 + }
609 + buffer_json_object_close(wb);
610 + }
611 + buffer_json_object_close(wb); // actor_types
612 +
613 + buffer_json_member_add_object(wb, "link_types");
614 + {
615 + buffer_json_member_add_object(wb, "streaming");
616 + {
617 + buffer_json_member_add_string(wb, "label", "Streaming");
618 + buffer_json_member_add_string(wb, "color_slot", "primary");
619 + buffer_json_member_add_double(wb, "width", 2);
620 + buffer_json_member_add_boolean(wb, "dash", false);
621 + buffer_json_member_add_double(wb, "opacity", 1.0);
622 + }
623 + buffer_json_object_close(wb);
624 +
625 + buffer_json_member_add_object(wb, "virtual");
626 + {
627 + buffer_json_member_add_string(wb, "label", "Virtual origin");
628 + buffer_json_member_add_string(wb, "color_slot", "warning");
629 + buffer_json_member_add_double(wb, "width", 1);
630 + buffer_json_member_add_boolean(wb, "dash", true);
631 + buffer_json_member_add_double(wb, "opacity", 0.7);
632 + }
633 + buffer_json_object_close(wb);
634 +
635 + buffer_json_member_add_object(wb, "stale");
636 + {
637 + buffer_json_member_add_string(wb, "label", "Stale data");
638 + buffer_json_member_add_string(wb, "color_slot", "dim");
639 + buffer_json_member_add_double(wb, "width", 1);
640 + buffer_json_member_add_boolean(wb, "dash", true);
641 + buffer_json_member_add_double(wb, "opacity", 0.4);
642 + }
643 + buffer_json_object_close(wb);
644 + }
645 + buffer_json_object_close(wb); // link_types
646 +
647 + buffer_json_member_add_array(wb, "port_fields");
648 + {
649 + buffer_json_add_array_item_object(wb);
650 + buffer_json_member_add_string(wb, "key", "type");
651 + buffer_json_member_add_string(wb, "label", "Type");
652 + buffer_json_object_close(wb);
653 + }
654 + buffer_json_array_close(wb); // port_fields
655 +
656 + buffer_json_member_add_object(wb, "port_types");
657 + {
658 + buffer_json_member_add_object(wb, "streaming");
659 + {
660 + buffer_json_member_add_string(wb, "label", "Streaming child");
661 + buffer_json_member_add_string(wb, "color_slot", "primary");
662 + buffer_json_member_add_double(wb, "opacity", 1.0);
663 + }
664 + buffer_json_object_close(wb);
665 +
666 + buffer_json_member_add_object(wb, "virtual");
667 + {
668 + buffer_json_member_add_string(wb, "label", "Virtual node");
669 + buffer_json_member_add_string(wb, "color_slot", "warning");
670 + buffer_json_member_add_double(wb, "opacity", 1.0);
671 + }
672 + buffer_json_object_close(wb);
673 +
674 + buffer_json_member_add_object(wb, "stale");
675 + {
676 + buffer_json_member_add_string(wb, "label", "Stale node");
677 + buffer_json_member_add_string(wb, "color_slot", "dim");
678 + buffer_json_member_add_double(wb, "opacity", 0.5);
679 + }
680 + buffer_json_object_close(wb);
681 + }
682 + buffer_json_object_close(wb); // port_types
683 +
684 + buffer_json_member_add_object(wb, "legend");
685 + {
686 + buffer_json_member_add_array(wb, "actors");
687 + {
688 + buffer_json_add_array_item_object(wb);
689 + buffer_json_member_add_string(wb, "type", "parent");
690 + buffer_json_member_add_string(wb, "label", "Parent");
691 + buffer_json_object_close(wb);
692 +
693 + buffer_json_add_array_item_object(wb);
694 + buffer_json_member_add_string(wb, "type", "child");
695 + buffer_json_member_add_string(wb, "label", "Child");
696 + buffer_json_object_close(wb);
697 +
698 + buffer_json_add_array_item_object(wb);
699 + buffer_json_member_add_string(wb, "type", "vnode");
700 + buffer_json_member_add_string(wb, "label", "Virtual Node");
701 + buffer_json_object_close(wb);
702 +
703 + buffer_json_add_array_item_object(wb);
704 + buffer_json_member_add_string(wb, "type", "stale");
705 + buffer_json_member_add_string(wb, "label", "Stale Node");
706 + buffer_json_object_close(wb);
707 + }
708 + buffer_json_array_close(wb); // actors
709 +
710 + buffer_json_member_add_array(wb, "links");
711 + {
712 + buffer_json_add_array_item_object(wb);
713 + buffer_json_member_add_string(wb, "type", "streaming");
714 + buffer_json_member_add_string(wb, "label", "Streaming");
715 + buffer_json_object_close(wb);
716 +
717 + buffer_json_add_array_item_object(wb);
718 + buffer_json_member_add_string(wb, "type", "virtual");
719 + buffer_json_member_add_string(wb, "label", "Virtual origin");
720 + buffer_json_object_close(wb);
721 +
722 + buffer_json_add_array_item_object(wb);
723 + buffer_json_member_add_string(wb, "type", "stale");
724 + buffer_json_member_add_string(wb, "label", "Stale data");
725 + buffer_json_object_close(wb);
726 + }
727 + buffer_json_array_close(wb); // links
728 +
729 + buffer_json_member_add_array(wb, "ports");
730 + {
731 + buffer_json_add_array_item_object(wb);
732 + buffer_json_member_add_string(wb, "type", "streaming");
733 + buffer_json_member_add_string(wb, "label", "Streaming child");
734 + buffer_json_object_close(wb);
735 +
736 + buffer_json_add_array_item_object(wb);
737 + buffer_json_member_add_string(wb, "type", "virtual");
738 + buffer_json_member_add_string(wb, "label", "Virtual node");
739 + buffer_json_object_close(wb);
740 +
741 + buffer_json_add_array_item_object(wb);
742 + buffer_json_member_add_string(wb, "type", "stale");
743 + buffer_json_member_add_string(wb, "label", "Stale node");
744 + buffer_json_object_close(wb);
745 + }
746 + buffer_json_array_close(wb); // ports
747 + }
748 + buffer_json_object_close(wb); // legend
749 +
750 + buffer_json_member_add_string(wb, "actor_click_behavior", "highlight_path");
751 + }
752 + buffer_json_object_close(wb); // presentation
753 +
754 + if(!info_only) {
755 + // --- Phase 1: build parent_child_count dictionary from streaming_paths ---
756 + // A node is a parent if any other node's streaming_path contains it at position > 0
757 + DICTIONARY *parent_child_count = dictionary_create_advanced(
758 + DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
759 + NULL, sizeof(uint32_t));
760 + DICTIONARY *parent_descendants = dictionary_create_advanced(
761 + DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
762 + NULL, sizeof(struct streaming_topology_descendant_list));
763 +
764 + if(!parent_child_count || !parent_descendants) {
765 + if(parent_descendants)
766 + dictionary_destroy(parent_descendants);
767 + if(parent_child_count)
768 + dictionary_destroy(parent_child_count);
769 +
770 + return streaming_topology_return_error(wb, function_copy,
771 + HTTP_RESP_INTERNAL_SERVER_ERROR,
772 + "failed to allocate streaming topology dictionaries");
773 + }
774 + else {
775 +
776 + {
777 + RRDHOST *host;
778 + dfe_start_read(rrdhost_root_index, host) {
779 + // get all path entries at position > 0 (parents in the chain)
780 + ND_UUID path_ids[128];
781 + uint16_t n = streaming_topology_get_path_ids(host, 1, path_ids, 128);
782 + for(uint16_t i = 0; i < n; i++) {
783 + char guid[UUID_STR_LEN];
784 + if(!streaming_topology_uuid_guid(path_ids[i], guid, sizeof(guid)))
785 + continue;
786 +
787 + uint32_t *count = dictionary_get(parent_child_count, guid);
788 + if(count)
789 + (*count)++;
790 + else {
791 + uint32_t one = 1;
792 + dictionary_set(parent_child_count, guid, &one, sizeof(one));
793 + }
794 + }
795 +
796 + ND_UUID full_path_ids[128];
797 + uint16_t full_path_n = streaming_topology_get_path_ids(host, 0, full_path_ids, 128);
798 + ND_UUID empty_uuid = {};
799 +
800 + if(rrdhost_is_virtual(host)) {
801 + if(full_path_n > 0) {
802 + streaming_topology_descendants_append(parent_descendants,
803 + full_path_ids[0], host, STREAMING_TOPOLOGY_RECEIVED_VIRTUAL, true, empty_uuid);
804 +
805 + for(uint16_t i = 1; i < full_path_n; i++) {
806 + streaming_topology_descendants_append(parent_descendants,
807 + full_path_ids[i], host, STREAMING_TOPOLOGY_RECEIVED_STREAMING, false, full_path_ids[i - 1]);
808 + }
809 + }
810 + }
811 + else if(full_path_n > 0) {
812 + for(uint16_t i = 0; i < full_path_n; i++) {
813 + bool source_local = (i == 0);
814 + ND_UUID source_uuid = source_local ? empty_uuid : full_path_ids[i - 1];
815 + streaming_topology_descendants_append(parent_descendants,
816 + full_path_ids[i], host, STREAMING_TOPOLOGY_RECEIVED_STREAMING, source_local, source_uuid);
817 + }
818 + }
819 + else if(host != localhost) {
820 + streaming_topology_descendants_append(parent_descendants,
821 + localhost->host_id, host, STREAMING_TOPOLOGY_RECEIVED_STALE, false, empty_uuid);
822 + }
823 + }
824 + dfe_done(host);
825 + }
826 +
827 + buffer_json_member_add_object(wb, "data");
828 + {
829 + size_t actors_total = 0;
830 + size_t links_total = 0;
831 +
832 + buffer_json_member_add_string(wb, "schema_version", "2.0");
833 + buffer_json_member_add_string(wb, "source", "streaming");
834 + buffer_json_member_add_string(wb, "layer", "infra");
835 + char localhost_agent_id[256];
836 + streaming_topology_agent_id_for_host(localhost, localhost_agent_id, sizeof(localhost_agent_id));
837 + buffer_json_member_add_string(wb, "agent_id", localhost_agent_id);
838 + buffer_json_member_add_datetime_rfc3339(wb, "collected_at", now_ut, true);
839 +
840 + // --- Phase 3: emit actors ---
841 + buffer_json_member_add_array(wb, "actors");
842 + {
843 + RRDHOST *host;
844 + dfe_start_read(rrdhost_root_index, host) {
845 + RRDHOST_STATUS s;
846 + rrdhost_status(host, now, &s, RRDHOST_STATUS_ALL);
847 + const char *hostname = rrdhost_hostname(host);
848 + char host_actor_id[256];
849 + streaming_topology_actor_id_for_host(host, host_actor_id, sizeof(host_actor_id));
850 +
851 + // classify node type by role
852 + // stale = no connections ever (ARCHIVED status with 0 connections)
853 + // vnode, parent, child determined by role in topology
854 + const char *node_type;
855 + if(rrdhost_is_virtual(host))
856 + node_type = "vnode";
857 + else if(host != localhost && s.ingest.status == RRDHOST_INGEST_STATUS_ARCHIVED)
858 + node_type = "stale";
859 + else {
860 + uint32_t *cc = streaming_topology_parent_child_count_get(parent_child_count, host);
861 + node_type = (cc && *cc > 0) ? "parent" : "child";
862 + }
863 +
864 + // apply filters
865 + const char *ingest_status_str = rrdhost_ingest_status_to_string(s.ingest.status);
866 + const char *stream_status_str = rrdhost_streaming_status_to_string(s.stream.status);
867 +
868 + if(!value_in_csv(filter_node_type, node_type))
869 + continue;
870 + if(!value_in_csv(filter_ingest_status, ingest_status_str))
871 + continue;
872 + if(!value_in_csv(filter_stream_status, stream_status_str))
873 + continue;
874 +
875 + uint32_t child_count = 0;
876 + {
877 + uint32_t *cc = streaming_topology_parent_child_count_get(parent_child_count, host);
878 + if(cc) child_count = *cc;
879 + }
880 +
881 + // compute severity (same logic as table function)
882 + const char *severity = "normal";
883 + if(!rrdhost_option_check(host, RRDHOST_OPTION_EPHEMERAL_HOST)) {
884 + switch(s.ingest.status) {
885 + case RRDHOST_INGEST_STATUS_OFFLINE:
886 + case RRDHOST_INGEST_STATUS_ARCHIVED:
887 + severity = "critical";
888 + break;
889 + default:
890 + break;
891 + }
892 + if(strcmp(severity, "normal") == 0) {
893 + switch(s.stream.status) {
894 + case RRDHOST_STREAM_STATUS_OFFLINE:
895 + if(s.stream.reason != STREAM_HANDSHAKE_SP_NO_DESTINATION)
896 + severity = "warning";
897 + break;
898 + default:
899 + break;
900 + }
901 + }
902 + }
903 +
904 + actors_total++;
905 + buffer_json_add_array_item_object(wb);
906 + {
907 + buffer_json_member_add_string(wb, "actor_id", host_actor_id);
908 + buffer_json_member_add_string(wb, "actor_type", node_type);
909 + buffer_json_member_add_string(wb, "layer", "infra");
910 + buffer_json_member_add_string(wb, "source", "streaming");
911 + streaming_topology_add_host_match(wb, host);
912 +
913 + buffer_json_member_add_object(wb, "attributes");
914 + {
915 + // intrinsic identity
916 + buffer_json_member_add_string(wb, "display_name", hostname);
917 + buffer_json_member_add_string(wb, "node_type", node_type);
918 + buffer_json_member_add_string(wb, "severity", severity);
919 + buffer_json_member_add_uint64(wb, "child_count", child_count);
920 + buffer_json_member_add_string(wb, "ephemerality", rrdhost_option_check(host, RRDHOST_OPTION_EPHEMERAL_HOST) ? "ephemeral" : "permanent");
921 + buffer_json_member_add_string(wb, "agent_name", rrdhost_program_name(host));
922 + buffer_json_member_add_string(wb, "agent_version", rrdhost_program_version(host));
923 +
924 + // system info (intrinsic hardware/OS fields)
925 + rrdhost_system_info_to_json_object_fields(wb, s.host->system_info);
926 +
927 + // health/alerts (intrinsic to the node running health)
928 + buffer_json_member_add_string(wb, "health_status", rrdhost_health_status_to_string(s.health.status));
929 + if(s.health.status == RRDHOST_HEALTH_STATUS_RUNNING) {
930 + buffer_json_member_add_uint64(wb, "health_critical", s.health.alerts.critical);
931 + buffer_json_member_add_uint64(wb, "health_warning", s.health.alerts.warning);
932 + buffer_json_member_add_uint64(wb, "health_clear", s.health.alerts.clear);
933 + }
934 + else {
935 + buffer_json_member_add_uint64(wb, "health_critical", 0);
936 + buffer_json_member_add_uint64(wb, "health_warning", 0);
937 + buffer_json_member_add_uint64(wb, "health_clear", 0);
938 + }
939 + }
940 + buffer_json_object_close(wb); // attributes
941 +
942 + buffer_json_member_add_object(wb, "labels");
943 + {
944 + // topology-specific labels (used for filtering)
945 + buffer_json_member_add_string(wb, "hostname", hostname);
946 + buffer_json_member_add_string(wb, "node_type", node_type);
947 + buffer_json_member_add_string(wb, "severity", severity);
948 + buffer_json_member_add_string(wb, "ephemerality", rrdhost_option_check(host, RRDHOST_OPTION_EPHEMERAL_HOST) ? "ephemeral" : "permanent");
949 + buffer_json_member_add_string(wb, "ingest_status", rrdhost_ingest_status_to_string(s.ingest.status));
950 + buffer_json_member_add_string(wb, "stream_status", rrdhost_streaming_status_to_string(s.stream.status));
951 + buffer_json_member_add_string(wb, "ml_status", rrdhost_ml_status_to_string(s.ml.status));
952 + buffer_json_member_add_string(wb, "display_name", hostname);
953 +
954 + // Host labels are nested to avoid collisions with reserved
955 + // topology label keys such as hostname/node_type/severity.
956 + buffer_json_member_add_object(wb, "host_labels");
957 + {
958 + rrdlabels_to_buffer_json_members(host->rrdlabels, wb);
959 + }
960 + buffer_json_object_close(wb); // host_labels
961 + }
962 + buffer_json_object_close(wb); // labels
963 +
964 + // streaming_path: array of actor_ids for highlight_path
965 + {
966 + ND_UUID path_ids[128];
967 + uint16_t path_n = streaming_topology_get_path_ids(host, 0, path_ids, 128);
968 + buffer_json_member_add_array(wb, "streaming_path");
969 + for(uint16_t pi = 0; pi < path_n; pi++) {
970 + char path_actor_id[256];
971 + streaming_topology_actor_id_for_uuid(path_ids[pi], path_actor_id, sizeof(path_actor_id));
972 + buffer_json_add_array_item_string(wb, path_actor_id);
973 + }
974 + buffer_json_array_close(wb);
975 + }
976 +
977 + // received_nodes: all nodes this parent receives data for (drives bullet count + type)
978 + if(child_count > 0) {
979 + buffer_json_member_add_array(wb, "received_nodes");
980 + struct streaming_topology_descendant_list *received_nodes =
981 + streaming_topology_descendants_get(parent_descendants, host);
982 + if(received_nodes) {
983 + for(size_t i = 0; i < received_nodes->used; i++) {
984 + struct streaming_topology_descendant *descendant = &received_nodes->items[i];
985 + RRDHOST *rn_host = descendant->host;
986 +
987 + if(rn_host == host)
988 + continue;
989 +
990 + buffer_json_add_array_item_object(wb);
991 + buffer_json_member_add_string(wb, "name", rrdhost_hostname(rn_host));
992 + buffer_json_member_add_string(wb, "type",
993 + streaming_topology_received_type_to_string((enum streaming_topology_received_type)descendant->type));
994 + buffer_json_object_close(wb);
995 + }
996 + }
997 + buffer_json_array_close(wb);
998 + }
999 +
1000 + // --- per-actor tables ---
1001 + buffer_json_member_add_object(wb, "tables");
1002 + {
1003 + bool is_observer = (host == localhost);
1004 +
1005 + // PARENT tables: inbound, outbound, retention
1006 + if(child_count > 0) {
1007 + // inbound table: ALL nodes this parent has data for
1008 + if(is_observer) {
1009 + buffer_json_member_add_array(wb, "inbound");
1010 + {
1011 + RRDHOST *ih;
1012 + dfe_start_read(rrdhost_root_index, ih) {
1013 + char ih_actor_id[256];
1014 + streaming_topology_actor_id_for_host(ih, ih_actor_id, sizeof(ih_actor_id));
1015 +
1016 + RRDHOST_STATUS ihs;
1017 + rrdhost_status(ih, now, &ihs, RRDHOST_STATUS_ALL);
1018 +
1019 + // determine node type
1020 + const char *ih_node_type;
1021 + if(rrdhost_is_virtual(ih))
1022 + ih_node_type = "vnode";
1023 + else if(ih != localhost && ihs.ingest.status == RRDHOST_INGEST_STATUS_ARCHIVED)
1024 + ih_node_type = "stale";
1025 + else {
1026 + uint32_t *cc = streaming_topology_parent_child_count_get(parent_child_count, ih);
1027 + ih_node_type = (cc && *cc > 0) ? "parent" : "child";
1028 + }
1029 +
1030 + // determine source: who sends this host's data to us
1031 + const char *src_hostname = NULL;
1032 + char src_actor_id[256] = "";
1033 + if(ih == host) {
1034 + src_hostname = "local";
1035 + }
1036 + else if(rrdhost_is_virtual(ih)) {
1037 + src_hostname = "local";
1038 + }
1039 + else {
1040 + ND_UUID ih_path[128];
1041 + uint16_t ih_pn = streaming_topology_get_path_ids(ih, 0, ih_path, 128);
1042 + char src_guid[UUID_STR_LEN] = "";
1043 + for(uint16_t pi = 0; pi < ih_pn; pi++) {
1044 + if(UUIDeq(ih_path[pi], host->host_id) && pi > 0) {
1045 + uuid_unparse_lower(ih_path[pi - 1].uuid, src_guid);
1046 + streaming_topology_actor_id_from_guid(src_guid, src_actor_id, sizeof(src_actor_id));
1047 + RRDHOST *src = rrdhost_find_by_guid(src_guid);
1048 + src_hostname = src ? rrdhost_hostname(src) : src_guid;
1049 + break;
1050 + }
1051 + }
1052 + if(!src_hostname) src_hostname = "unknown";
1053 + }
1054 +
1055 + buffer_json_add_array_item_object(wb);
1056 + buffer_json_member_add_string(wb, "name", rrdhost_hostname(ih));
1057 + buffer_json_member_add_string(wb, "name_id", ih_actor_id);
1058 + buffer_json_member_add_string(wb, "received_from", src_hostname);
1059 + if(src_actor_id[0])
1060 + buffer_json_member_add_string(wb, "received_from_id", src_actor_id);
1061 + buffer_json_member_add_string(wb, "node_type", ih_node_type);
1062 + buffer_json_member_add_string(wb, "ingest_status", rrdhost_ingest_status_to_string(ihs.ingest.status));
1063 + buffer_json_member_add_int64(wb, "hops", ihs.ingest.hops);
1064 + buffer_json_member_add_uint64(wb, "collected_metrics", ihs.ingest.collected.metrics);
1065 + buffer_json_member_add_uint64(wb, "collected_instances", ihs.ingest.collected.instances);
1066 + buffer_json_member_add_uint64(wb, "collected_contexts", ihs.ingest.collected.contexts);
1067 + buffer_json_member_add_double(wb, "repl_completion", ihs.ingest.replication.completion);
1068 + buffer_json_member_add_time_t(wb, "ingest_age", ihs.ingest.since ? ihs.now - ihs.ingest.since : 0);
1069 + buffer_json_member_add_string(wb, "ssl", ihs.ingest.ssl ? "SSL" : "PLAIN");
1070 + buffer_json_member_add_uint64(wb, "alerts_critical",
1071 + ihs.health.status == RRDHOST_HEALTH_STATUS_RUNNING ? ihs.health.alerts.critical : 0);
1072 + buffer_json_member_add_uint64(wb, "alerts_warning",
1073 + ihs.health.status == RRDHOST_HEALTH_STATUS_RUNNING ? ihs.health.alerts.warning : 0);
1074 + buffer_json_object_close(wb);
1075 + }
1076 + dfe_done(ih);
1077 + }
1078 + buffer_json_array_close(wb); // inbound
1079 + }
1080 + else {
1081 + // non-observer parent: use precomputed descendants
1082 + buffer_json_member_add_array(wb, "inbound");
1083 + {
1084 + struct streaming_topology_descendant_list *inbound_nodes =
1085 + streaming_topology_descendants_get(parent_descendants, host);
1086 + if(inbound_nodes) {
1087 + for(size_t i = 0; i < inbound_nodes->used; i++) {
1088 + struct streaming_topology_descendant *descendant = &inbound_nodes->items[i];
1089 + RRDHOST *ih = descendant->host;
1090 + const char *src_hostname = NULL;
1091 + char src_actor_id[256] = "";
1092 + RRDHOST_STATUS ihs;
1093 +
1094 + if(descendant->source_local)
1095 + src_hostname = "local";
1096 + else if(!UUIDiszero(descendant->source_uuid)) {
1097 + char src_guid[UUID_STR_LEN];
1098 + uuid_unparse_lower(descendant->source_uuid.uuid, src_guid);
1099 + streaming_topology_actor_id_from_guid(src_guid, src_actor_id, sizeof(src_actor_id));
1100 + RRDHOST *src = rrdhost_find_by_guid(src_guid);
1101 + src_hostname = src ? rrdhost_hostname(src) : src_guid;
1102 + }
1103 + else
1104 + src_hostname = "unknown";
1105 +
1106 + char ih_actor_id[256];
1107 + streaming_topology_actor_id_for_host(ih, ih_actor_id, sizeof(ih_actor_id));
1108 +
1109 + rrdhost_status(ih, now, &ihs, RRDHOST_STATUS_ALL);
1110 +
1111 + const char *ih_node_type;
1112 + if(rrdhost_is_virtual(ih))
1113 + ih_node_type = "vnode";
1114 + else if(ih != localhost && ihs.ingest.status == RRDHOST_INGEST_STATUS_ARCHIVED)
1115 + ih_node_type = "stale";
1116 + else {
1117 + uint32_t *cc = streaming_topology_parent_child_count_get(parent_child_count, ih);
1118 + ih_node_type = (cc && *cc > 0) ? "parent" : "child";
1119 + }
1120 +
1121 + buffer_json_add_array_item_object(wb);
1122 + buffer_json_member_add_string(wb, "name", rrdhost_hostname(ih));
1123 + buffer_json_member_add_string(wb, "name_id", ih_actor_id);
1124 + buffer_json_member_add_string(wb, "received_from", src_hostname);
1125 + if(src_actor_id[0])
1126 + buffer_json_member_add_string(wb, "received_from_id", src_actor_id);
1127 + buffer_json_member_add_string(wb, "node_type", ih_node_type);
1128 + buffer_json_member_add_string(wb, "ingest_status", rrdhost_ingest_status_to_string(ihs.ingest.status));
1129 + buffer_json_member_add_int64(wb, "hops", ihs.ingest.hops);
1130 + buffer_json_member_add_uint64(wb, "collected_metrics", ihs.ingest.collected.metrics);
1131 + buffer_json_member_add_uint64(wb, "collected_instances", ihs.ingest.collected.instances);
1132 + buffer_json_member_add_uint64(wb, "collected_contexts", ihs.ingest.collected.contexts);
1133 + buffer_json_member_add_double(wb, "repl_completion", ihs.ingest.replication.completion);
1134 + buffer_json_member_add_time_t(wb, "ingest_age", ihs.ingest.since ? ihs.now - ihs.ingest.since : 0);
1135 + buffer_json_member_add_string(wb, "ssl", ihs.ingest.ssl ? "SSL" : "PLAIN");
1136 + buffer_json_member_add_uint64(wb, "alerts_critical",
1137 + ihs.health.status == RRDHOST_HEALTH_STATUS_RUNNING ? ihs.health.alerts.critical : 0);
1138 + buffer_json_member_add_uint64(wb, "alerts_warning",
1139 + ihs.health.status == RRDHOST_HEALTH_STATUS_RUNNING ? ihs.health.alerts.warning : 0);
1140 + buffer_json_object_close(wb);
1141 + }
1142 + }
1143 + }
1144 + buffer_json_array_close(wb); // inbound
1145 + }
1146 +
1147 + // retention table: ALL nodes with DB retention (including localhost)
1148 + if(is_observer) {
1149 + buffer_json_member_add_array(wb, "retention");
1150 + {
1151 + RRDHOST *rh;
1152 + dfe_start_read(rrdhost_root_index, rh) {
1153 + RRDHOST_STATUS rs;
1154 + rrdhost_status(rh, now, &rs, RRDHOST_STATUS_ALL);
1155 +
1156 + if(!rs.db.first_time_s && !rs.db.last_time_s)
1157 + continue;
1158 +
1159 + char rh_actor_id[256];
1160 + streaming_topology_actor_id_for_host(rh, rh_actor_id, sizeof(rh_actor_id));
1161 +
1162 + buffer_json_add_array_item_object(wb);
1163 + buffer_json_member_add_string(wb, "name", rrdhost_hostname(rh));
1164 + buffer_json_member_add_string(wb, "name_id", rh_actor_id);
1165 + buffer_json_member_add_string(wb, "db_status", rrdhost_db_status_to_string(rs.db.status));
1166 + buffer_json_member_add_uint64(wb, "db_from", rs.db.first_time_s * MSEC_PER_SEC);
1167 + buffer_json_member_add_uint64(wb, "db_to", rs.db.last_time_s * MSEC_PER_SEC);
1168 + if(rs.db.first_time_s && rs.db.last_time_s && rs.db.last_time_s > rs.db.first_time_s)
1169 + buffer_json_member_add_uint64(wb, "db_duration", rs.db.last_time_s - rs.db.first_time_s);
1170 + else
1171 + buffer_json_member_add_uint64(wb, "db_duration", 0);
1172 + buffer_json_member_add_uint64(wb, "db_metrics", rs.db.metrics);
1173 + buffer_json_member_add_uint64(wb, "db_instances", rs.db.instances);
1174 + buffer_json_member_add_uint64(wb, "db_contexts", rs.db.contexts);
1175 + buffer_json_object_close(wb);
1176 + }
1177 + dfe_done(rh);
1178 + }
1179 + buffer_json_array_close(wb); // retention
1180 + }
1181 + }
1182 +
1183 + // outbound table: ALL nodes this actor streams to its parent
1184 + if(is_observer && s.stream.status != RRDHOST_STREAM_STATUS_DISABLED) {
1185 + bool stream_connected = (s.stream.status == RRDHOST_STREAM_STATUS_ONLINE ||
1186 + s.stream.status == RRDHOST_STREAM_STATUS_REPLICATING);
1187 +
1188 + // find our streaming destination (only when connected)
1189 + const char *dst_hostname = NULL;
1190 + char dst_actor_id[256] = "";
1191 + if(stream_connected) {
1192 + ND_UUID path_ids[16];
1193 + uint16_t n_ids = rrdhost_stream_path_get_host_ids(host, 0, path_ids, 16);
1194 + for(uint16_t pi = 0; pi < n_ids; pi++) {
1195 + if(!UUIDeq(path_ids[pi], host->host_id)) {
1196 + char guid[UUID_STR_LEN];
1197 + uuid_unparse_lower(path_ids[pi].uuid, guid);
1198 + streaming_topology_actor_id_from_guid(guid, dst_actor_id, sizeof(dst_actor_id));
1199 + RRDHOST *dst_host = rrdhost_find_by_guid(guid);
1200 + if(dst_host)
1201 + dst_hostname = rrdhost_hostname(dst_host);
1202 + break;
1203 + }
1204 + }
1205 + if(!dst_hostname) dst_hostname = s.stream.peers.peer.ip;
1206 + }
1207 +
1208 + buffer_json_member_add_array(wb, "outbound");
1209 + {
1210 + RRDHOST *oh;
1211 + dfe_start_read(rrdhost_root_index, oh) {
1212 + char oh_actor_id[256];
1213 + streaming_topology_actor_id_for_host(oh, oh_actor_id, sizeof(oh_actor_id));
1214 +
1215 + RRDHOST_STATUS ohs;
1216 + rrdhost_status(oh, now, &ohs, RRDHOST_STATUS_ALL);
1217 +
1218 + // determine node type
1219 + const char *oh_node_type;
1220 + if(rrdhost_is_virtual(oh))
1221 + oh_node_type = "vnode";
1222 + else if(ohs.ingest.status == RRDHOST_INGEST_STATUS_ARCHIVED)
1223 + oh_node_type = "stale";
1224 + else {
1225 + uint32_t *cc = streaming_topology_parent_child_count_get(parent_child_count, oh);
1226 + oh_node_type = (cc && *cc > 0) ? "parent" : "child";
1227 + }
1228 +
1229 + RRDHOST_STREAMING_STATUS oh_ss = (oh == host) ? s.stream.status : ohs.stream.status;
1230 + bool oh_streaming = (oh_ss == RRDHOST_STREAM_STATUS_ONLINE || oh_ss == RRDHOST_STREAM_STATUS_REPLICATING);
1231 +
1232 + buffer_json_add_array_item_object(wb);
1233 + buffer_json_member_add_string(wb, "name", rrdhost_hostname(oh));
1234 + buffer_json_member_add_string(wb, "name_id", oh_actor_id);
1235 + if(dst_hostname && oh_streaming) {
1236 + buffer_json_member_add_string(wb, "streamed_to", dst_hostname);
1237 + if(dst_actor_id[0])
1238 + buffer_json_member_add_string(wb, "streamed_to_id", dst_actor_id);
1239 + }
1240 + buffer_json_member_add_string(wb, "node_type", oh_node_type);
1241 + buffer_json_member_add_string(wb, "stream_status", rrdhost_streaming_status_to_string(oh_ss));
1242 +
1243 + if(oh == host && oh_streaming) {
1244 + buffer_json_member_add_uint64(wb, "hops", s.stream.hops);
1245 + buffer_json_member_add_string(wb, "ssl", s.stream.ssl ? "SSL" : "PLAIN");
1246 + buffer_json_member_add_string(wb, "compression", s.stream.compression ? "COMPRESSED" : "UNCOMPRESSED");
1247 + }
1248 +
1249 + buffer_json_object_close(wb);
1250 + }
1251 + dfe_done(oh);
1252 + }
1253 + buffer_json_array_close(wb); // outbound
1254 + }
1255 + else if(!is_observer && child_count > 0) {
1256 + // non-observer parent: use precomputed descendants
1257 + // find this parent's outbound destination (next hop in its own path)
1258 + const char *dst_hostname = NULL;
1259 +
1260 + buffer_json_member_add_array(wb, "outbound");
1261 + {
1262 + char dst_actor_id[256] = "";
1263 + char dst_hostname_fallback[UUID_STR_LEN] = "";
1264 + ND_UUID host_path[128];
1265 + uint16_t host_pn = streaming_topology_get_path_ids(host, 0, host_path, 128);
1266 + for(uint16_t pi = 0; pi < host_pn; pi++) {
1267 + if(UUIDeq(host_path[pi], host->host_id) && pi + 1 < host_pn) {
1268 + uuid_unparse_lower(host_path[pi + 1].uuid, dst_hostname_fallback);
1269 + streaming_topology_actor_id_from_guid(dst_hostname_fallback, dst_actor_id, sizeof(dst_actor_id));
1270 + RRDHOST *dst = rrdhost_find_by_guid(dst_hostname_fallback);
1271 + dst_hostname = dst ? rrdhost_hostname(dst) : dst_hostname_fallback;
1272 + break;
1273 + }
1274 + }
1275 +
1276 + struct streaming_topology_descendant_list *outbound_nodes =
1277 + streaming_topology_descendants_get(parent_descendants, host);
1278 + if(outbound_nodes) {
1279 + for(size_t i = 0; i < outbound_nodes->used; i++) {
1280 + RRDHOST *oh = outbound_nodes->items[i].host;
1281 + char oh_actor_id[256];
1282 + RRDHOST_STATUS ohs;
1283 + streaming_topology_actor_id_for_host(oh, oh_actor_id, sizeof(oh_actor_id));
1284 +
1285 + rrdhost_status(oh, now, &ohs, RRDHOST_STATUS_ALL);
1286 +
1287 + const char *oh_node_type;
1288 + if(rrdhost_is_virtual(oh))
1289 + oh_node_type = "vnode";
1290 + else if(ohs.ingest.status == RRDHOST_INGEST_STATUS_ARCHIVED)
1291 + oh_node_type = "stale";
1292 + else {
1293 + uint32_t *cc = streaming_topology_parent_child_count_get(parent_child_count, oh);
1294 + oh_node_type = (cc && *cc > 0) ? "parent" : "child";
1295 + }
1296 +
1297 + RRDHOST_STREAMING_STATUS oh_ss = (oh == host) ? s.stream.status : ohs.stream.status;
1298 + bool oh_streaming = (oh_ss == RRDHOST_STREAM_STATUS_ONLINE || oh_ss == RRDHOST_STREAM_STATUS_REPLICATING);
1299 +
1300 + buffer_json_add_array_item_object(wb);
1301 + buffer_json_member_add_string(wb, "name", rrdhost_hostname(oh));
1302 + buffer_json_member_add_string(wb, "name_id", oh_actor_id);
1303 + if(dst_hostname && oh_streaming) {
1304 + buffer_json_member_add_string(wb, "streamed_to", dst_hostname);
1305 + if(dst_actor_id[0])
1306 + buffer_json_member_add_string(wb, "streamed_to_id", dst_actor_id);
1307 + }
1308 + buffer_json_member_add_string(wb, "node_type", oh_node_type);
1309 + buffer_json_member_add_string(wb, "stream_status", rrdhost_streaming_status_to_string(oh_ss));
1310 + if(oh == host && oh_streaming) {
1311 + buffer_json_member_add_uint64(wb, "hops", s.stream.hops);
1312 + buffer_json_member_add_string(wb, "ssl", s.stream.ssl ? "SSL" : "PLAIN");
1313 + buffer_json_member_add_string(wb, "compression", s.stream.compression ? "COMPRESSED" : "UNCOMPRESSED");
1314 + }
1315 + buffer_json_object_close(wb);
1316 + }
1317 + }
1318 + }
1319 + buffer_json_array_close(wb); // outbound
1320 + }
1321 +
1322 + // streaming_path table: per-hop data (uses the public API)
1323 + rrdhost_stream_path_to_json(wb, host, "streaming_path", false);
1324 +
1325 + // retention table for non-parent actors: rows = observers (parents)
1326 + // in single-observer mode, 1 row from localhost
1327 + if(child_count == 0) {
1328 + buffer_json_member_add_array(wb, "retention");
1329 + {
1330 + char observer_actor_id[256];
1331 + streaming_topology_actor_id_for_host(localhost, observer_actor_id, sizeof(observer_actor_id));
1332 +
1333 + buffer_json_add_array_item_object(wb);
1334 + buffer_json_member_add_string(wb, "name", rrdhost_hostname(localhost));
1335 + buffer_json_member_add_string(wb, "name_id", observer_actor_id);
1336 + buffer_json_member_add_string(wb, "db_status", rrdhost_db_status_to_string(s.db.status));
1337 + buffer_json_member_add_uint64(wb, "db_from", s.db.first_time_s * MSEC_PER_SEC);
1338 + buffer_json_member_add_uint64(wb, "db_to", s.db.last_time_s * MSEC_PER_SEC);
1339 + if(s.db.first_time_s && s.db.last_time_s && s.db.last_time_s > s.db.first_time_s)
1340 + buffer_json_member_add_uint64(wb, "db_duration", s.db.last_time_s - s.db.first_time_s);
1341 + else
1342 + buffer_json_member_add_uint64(wb, "db_duration", 0);
1343 + buffer_json_member_add_uint64(wb, "db_metrics", s.db.metrics);
1344 + buffer_json_member_add_uint64(wb, "db_instances", s.db.instances);
1345 + buffer_json_member_add_uint64(wb, "db_contexts", s.db.contexts);
1346 + buffer_json_object_close(wb);
1347 + }
1348 + buffer_json_array_close(wb); // retention
1349 + }
1350 + }
1351 + buffer_json_object_close(wb); // tables
1352 + }
1353 + buffer_json_object_close(wb); // actor
1354 + }
1355 + dfe_done(host);
1356 + }
1357 + buffer_json_array_close(wb); // actors
1358 +
1359 + // --- Phase 4: emit links from streaming_path ---
1360 + // nodes with an active path get streaming/virtual links
1361 + // nodes without a path (stale/offline) get a stale link to localhost
1362 + char localhost_actor_id[256];
1363 + streaming_topology_actor_id_for_host(localhost, localhost_actor_id, sizeof(localhost_actor_id));
1364 +
1365 + buffer_json_member_add_array(wb, "links");
1366 + {
1367 + RRDHOST *host;
1368 + dfe_start_read(rrdhost_root_index, host) {
1369 + // skip localhost — it's the root of the tree
1370 + if(host == localhost)
1371 + continue;
1372 +
1373 + RRDHOST_STATUS s;
1374 + rrdhost_status(host, now, &s, RRDHOST_STATUS_ALL);
1375 +
1376 + // apply same filters as actors
1377 + const char *node_type;
1378 + if(rrdhost_is_virtual(host))
1379 + node_type = "vnode";
1380 + else if(s.ingest.status == RRDHOST_INGEST_STATUS_ARCHIVED)
1381 + node_type = "stale";
1382 + else {
1383 + uint32_t *cc = streaming_topology_parent_child_count_get(parent_child_count, host);
1384 + node_type = (cc && *cc > 0) ? "parent" : "child";
1385 + }
1386 + if(!value_in_csv(filter_node_type, node_type))
1387 + continue;
1388 + if(!value_in_csv(filter_ingest_status, rrdhost_ingest_status_to_string(s.ingest.status)))
1389 + continue;
1390 + if(!value_in_csv(filter_stream_status, rrdhost_streaming_status_to_string(s.stream.status)))
1391 + continue;
1392 +
1393 + char host_actor_id[256];
1394 + streaming_topology_actor_id_for_host(host, host_actor_id, sizeof(host_actor_id));
1395 +
1396 + bool is_vnode = rrdhost_is_virtual(host);
1397 +
1398 + // determine link target and type from streaming_path
1399 + const char *link_type = NULL;
1400 + char target_actor_id[256] = "";
1401 +
1402 + ND_UUID link_ids[2];
1403 + uint16_t link_n = streaming_topology_get_path_ids(host, 0, link_ids, 2);
1404 +
1405 + if(is_vnode && link_n >= 2) {
1406 + // vnodes: path[0] is the originating agent
1407 + streaming_topology_actor_id_for_uuid(link_ids[0], target_actor_id, sizeof(target_actor_id));
1408 + link_type = "virtual";
1409 + }
1410 + else if(!is_vnode && link_n >= 2) {
1411 + // children/parents: path[1] is the direct parent
1412 + streaming_topology_actor_id_for_uuid(link_ids[1], target_actor_id, sizeof(target_actor_id));
1413 + link_type = "streaming";
1414 + }
1415 + else {
1416 + // no active path — stale link to localhost
1417 + snprintfz(target_actor_id, sizeof(target_actor_id), "%s", localhost_actor_id);
1418 + link_type = "stale";
1419 + }
1420 +
1421 + const char *hostname = rrdhost_hostname(host);
1422 +
1423 + links_total++;
1424 + buffer_json_add_array_item_object(wb);
1425 + {
1426 + buffer_json_member_add_string(wb, "layer", "infra");
1427 + buffer_json_member_add_string(wb, "protocol", "streaming");
1428 + buffer_json_member_add_string(wb, "link_type", link_type);
1429 + buffer_json_member_add_string(wb, "src_actor_id", host_actor_id);
1430 + buffer_json_member_add_string(wb, "dst_actor_id", target_actor_id);
1431 + buffer_json_member_add_string(wb, "state", rrdhost_ingest_status_to_string(s.ingest.status));
1432 + buffer_json_member_add_datetime_rfc3339(wb, "discovered_at",
1433 + ((uint64_t)(s.ingest.since ? s.ingest.since : now)) * USEC_PER_SEC, true);
1434 + buffer_json_member_add_datetime_rfc3339(wb, "last_seen", now_ut, true);
1435 +
1436 + buffer_json_member_add_object(wb, "dst");
1437 + {
1438 + buffer_json_member_add_object(wb, "attributes");
1439 + {
1440 + buffer_json_member_add_string(wb, "port_name", hostname);
1441 + }
1442 + buffer_json_object_close(wb);
1443 + }
1444 + buffer_json_object_close(wb);
1445 +
1446 + buffer_json_member_add_object(wb, "metrics");
1447 + {
1448 + buffer_json_member_add_uint64(wb, "hops", s.ingest.hops);
1449 + if(strcmp(link_type, "virtual") != 0) {
1450 + buffer_json_member_add_uint64(wb, "connections", s.host->stream.rcv.status.connections);
1451 + buffer_json_member_add_uint64(wb, "replication_instances", s.ingest.replication.instances);
1452 + buffer_json_member_add_double(wb, "replication_completion", s.ingest.replication.completion);
1453 + buffer_json_member_add_uint64(wb, "collected_metrics", s.ingest.collected.metrics);
1454 + buffer_json_member_add_uint64(wb, "collected_instances", s.ingest.collected.instances);
1455 + buffer_json_member_add_uint64(wb, "collected_contexts", s.ingest.collected.contexts);
1456 + }
1457 + }
1458 + buffer_json_object_close(wb); // metrics
1459 + }
1460 + buffer_json_object_close(wb); // link
1461 + }
1462 + dfe_done(host);
1463 + }
1464 + buffer_json_array_close(wb); // links
1465 +
1466 + buffer_json_member_add_object(wb, "stats");
1467 + {
1468 + buffer_json_member_add_uint64(wb, "actors_total", actors_total);
1469 + buffer_json_member_add_uint64(wb, "links_total", links_total);
1470 + }
1471 + buffer_json_object_close(wb); // stats
1472 + }
1473 + buffer_json_object_close(wb); // data
1474 +
1475 + struct streaming_topology_descendant_list *descendants;
1476 + dfe_start_write(parent_descendants, descendants) {
1477 + freez(descendants->items);
1478 + descendants->items = NULL;
1479 + descendants->used = 0;
1480 + descendants->size = 0;
1481 + }
1482 + dfe_done(descendants);
1483 + dictionary_destroy(parent_descendants);
1484 + dictionary_destroy(parent_child_count);
1485 + }
1486 + }
1487 +
1488 + buffer_json_member_add_time_t(wb, "expires", now_realtime_sec() + STREAMING_FUNCTION_UPDATE_EVERY);
1489 + buffer_json_finalize(wb);
1490 + freez(function_copy);
1491 + return HTTP_RESP_OK;
1492 +}
1493 +
1494
1495 int function_streaming(BUFFER *wb, const char *function __maybe_unused, BUFFER *payload __maybe_unused, const char *source __maybe_unused) {
1496
@@ -26,7 +1503,7 @@ int function_streaming(BUFFER *wb, const char *function __maybe_unused, BUFFER *
1503 buffer_json_member_add_string(wb, "hostname", rrdhost_hostname(localhost));
1504 buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK);
1505 buffer_json_member_add_string(wb, "type", "table");
29 - buffer_json_member_add_time_t(wb, "update_every", 1);
1506 + buffer_json_member_add_time_t(wb, "update_every", STREAMING_FUNCTION_UPDATE_EVERY);
1507 buffer_json_member_add_boolean(wb, "has_history", false);
1508 buffer_json_member_add_string(wb, "help", RRDFUNCTIONS_STREAMING_HELP);
1509 buffer_json_member_add_array(wb, "data");
@@ -971,7 +2448,7 @@ int function_streaming(BUFFER *wb, const char *function __maybe_unused, BUFFER *
2448 }
2449 buffer_json_object_close(wb); // group_by
2450
974 - buffer_json_member_add_time_t(wb, "expires", now_realtime_sec() + 1);
2451 + buffer_json_member_add_time_t(wb, "expires", now_realtime_sec() + STREAMING_FUNCTION_UPDATE_EVERY);
2452 buffer_json_finalize(wb);
2453
2454 return HTTP_RESP_OK;
src/web/api/functions/function-streaming.h
+2
@@ -6,7 +6,9 @@
6 #include "database/rrd.h"
7
8 #define RRDFUNCTIONS_STREAMING_HELP "Shows real-time streaming connections and replication status between parent and child nodes, including connection health, data flow metrics, and ML status."
9 +#define RRDFUNCTIONS_STREAMING_TOPOLOGY_HELP "Shows streaming topology relations across Netdata agents, including directional parent/child links and transport metadata."
10
11 int function_streaming(BUFFER *wb, const char *function, BUFFER *payload, const char *source);
12 +int function_streaming_topology(BUFFER *wb, const char *function, BUFFER *payload, const char *source);
13
14 #endif //NETDATA_FUNCTION_STREAMING_H
src/web/api/functions/functions.c
+12
@@ -17,6 +17,18 @@ void global_functions_add(void) {
17 HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA,
18 function_streaming);
19
20 + rrd_function_add_inline(
21 + localhost,
22 + NULL,
23 + "topology:streaming",
24 + 10,
25 + RRDFUNCTIONS_PRIORITY_DEFAULT + 1,
26 + RRDFUNCTIONS_VERSION_DEFAULT,
27 + RRDFUNCTIONS_STREAMING_TOPOLOGY_HELP,
28 + "top",
29 + HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA,
30 + function_streaming_topology);
31 +
32 rrd_function_add_inline(
33 localhost,
34 NULL,