Browse Source

local-listeners improvements (#18798)

* - procfile adaptive allocations now support initial sizing of all buffers.
- procfile lines memory footprint almost halfed (changed size_t to uint32_t).
- procfile exposes basic statistics about its operations.
- local-sockets can now find namespace sockets from the /proc filesystem of the host (no need for setns()).
- local-sockets now have 2 modes for supporting namespace sockets collection, controlled by LOCAL_SOCKETS_USE_SETNS.
- local-sockets now uses procfile to read proc files.
- local-sockets with no-mnl did not receive sockets information from namespaces - fixed.
- local-listeners now provide detailed statistics for various aspects of its operation.
- local-listeners now provide different labels for libmnl timings and proc timings.
- posix spawn server now reopens logs as an external plugin (logging to DAEMON log from spawned callbacks is restored).
- posix spawn server executing a callback, now does not leak file descriptors to the child.

* fixed bug in mnl code - the socket cannot be reused; added option procfile to use profile instead of the getline

* eliminate excess function
Costa Tsaousis 4 months ago
parent
commit
dbd1b26bd0

+ 1 - 1
src/collectors/apps.plugin/apps_plugin.c

@@ -704,7 +704,7 @@ int main(int argc, char **argv) {
     }
 #endif /* NETDATA_INTERNAL_CHECKS */
 
-    procfile_adaptive_initial_allocation = 1;
+    procfile_set_adaptive_allocation(true, 0, 0, 0);
     os_get_system_HZ();
     os_get_system_cpus_uncached();
     apps_managers_and_aggregators_init(); // before parsing args!

+ 12 - 6
src/collectors/network-viewer.plugin/network-viewer.c

@@ -80,7 +80,7 @@ struct sockets_stats {
     } max;
 };
 
-static void local_socket_to_json_array(struct sockets_stats *st, LOCAL_SOCKET *n, uint64_t proc_self_net_ns_inode, bool aggregated) {
+static void local_socket_to_json_array(struct sockets_stats *st, const LOCAL_SOCKET *n, uint64_t proc_self_net_ns_inode, bool aggregated) {
     if(n->direction == SOCKET_DIRECTION_NONE)
         return;
 
@@ -156,7 +156,7 @@ static void local_socket_to_json_array(struct sockets_stats *st, LOCAL_SOCKET *n
             string_freez(u);
         }
 
-        struct socket_endpoint *server_endpoint;
+        const struct socket_endpoint *server_endpoint;
         const char *server_address;
         const char *client_address_space;
         const char *server_address_space;
@@ -240,7 +240,9 @@ static void local_socket_to_json_array(struct sockets_stats *st, LOCAL_SOCKET *n
     buffer_json_array_close(wb);
 }
 
-static void populate_aggregated_key(LOCAL_SOCKET *n) {
+static void populate_aggregated_key(const LOCAL_SOCKET *nn) {
+    LOCAL_SOCKET *n = (LOCAL_SOCKET *)nn;
+
     n->network_viewer.count = 1;
 
     n->network_viewer.aggregated_key.pid = n->pid;
@@ -269,7 +271,7 @@ static void populate_aggregated_key(LOCAL_SOCKET *n) {
     n->network_viewer.aggregated_key.remote_address_space = local_sockets_address_space(&n->remote);
 }
 
-static void local_sockets_cb_to_json(LS_STATE *ls, LOCAL_SOCKET *n, void *data) {
+static void local_sockets_cb_to_json(LS_STATE *ls, const LOCAL_SOCKET *n, void *data) {
     struct sockets_stats *st = data;
     populate_aggregated_key(n);
     local_socket_to_json_array(st, n, ls->proc_self_net_ns_inode, false);
@@ -280,12 +282,12 @@ static void local_sockets_cb_to_json(LS_STATE *ls, LOCAL_SOCKET *n, void *data)
 #define SUM_THEM_ALL(a, b) (a) += (b)
 #define OR_THEM_ALL(a, b) (a) |= (b)
 
-static void local_sockets_cb_to_aggregation(LS_STATE *ls __maybe_unused, LOCAL_SOCKET *n, void *data) {
+static void local_sockets_cb_to_aggregation(LS_STATE *ls __maybe_unused, const LOCAL_SOCKET *n, void *data) {
     SIMPLE_HASHTABLE_AGGREGATED_SOCKETS *ht = data;
 
     populate_aggregated_key(n);
     XXH64_hash_t hash = XXH3_64bits(&n->network_viewer.aggregated_key, sizeof(n->network_viewer.aggregated_key));
-    SIMPLE_HASHTABLE_SLOT_AGGREGATED_SOCKETS *sl = simple_hashtable_get_slot_AGGREGATED_SOCKETS(ht, hash, n, true);
+    SIMPLE_HASHTABLE_SLOT_AGGREGATED_SOCKETS *sl = simple_hashtable_get_slot_AGGREGATED_SOCKETS(ht, hash, (LOCAL_SOCKET *)n, true);
     LOCAL_SOCKET *t = SIMPLE_HASHTABLE_SLOT_DATA(sl);
     if(t) {
         t->network_viewer.count++;
@@ -511,7 +513,9 @@ void network_viewer_function(const char *transaction, char *function __maybe_unu
                 .max_errors = 10,
                 .max_concurrent_namespaces = 5,
             },
+#if defined(LOCAL_SOCKETS_USE_SETNS)
             .spawn_server = spawn_srv,
+#endif
             .stats = { 0 },
             .sockets_hashtable = { 0 },
             .local_ips_hashtable = { 0 },
@@ -963,11 +967,13 @@ int main(int argc __maybe_unused, char **argv __maybe_unused) {
     netdata_configured_host_prefix = getenv("NETDATA_HOST_PREFIX");
     if(verify_netdata_host_prefix(true) == -1) exit(1);
 
+#if defined(LOCAL_SOCKETS_USE_SETNS)
     spawn_srv = spawn_server_create(SPAWN_SERVER_OPTION_CALLBACK, "setns", local_sockets_spawn_server_callback, argc, (const char **)argv);
     if(spawn_srv == NULL) {
         fprintf(stderr, "Cannot create spawn server.\n");
         exit(1);
     }
+#endif
 
     uc = system_usernames_cache_init();
     sc = system_servicenames_cache_init();

+ 1 - 1
src/collectors/slabinfo.plugin/slabinfo.c

@@ -167,7 +167,7 @@ struct slabinfo *read_file_slabinfo() {
     slabdebug("   Read %lu lines from procfile", (unsigned long)lines);
     for(l = 2; l < lines; l++) {
         if (unlikely(procfile_linewords(ff, l) < 14)) {
-            slabdebug("    Line %zu has only %zu words, skipping", l, procfile_linewords(ff,l));
+            slabdebug("    Line %zu has only %zu words, skipping", l, (size_t)procfile_linewords(ff,l));
             continue;
         }
 

+ 51 - 3
src/collectors/utils/local_listeners.c

@@ -35,7 +35,9 @@ static const char *protocol_name(LOCAL_SOCKET *n) {
         return "UNKNOWN";
 }
 
-static void print_local_listeners(LS_STATE *ls __maybe_unused, LOCAL_SOCKET *n, void *data __maybe_unused) {
+static void print_local_listeners(LS_STATE *ls __maybe_unused, const LOCAL_SOCKET *nn, void *data __maybe_unused) {
+    LOCAL_SOCKET *n = (LOCAL_SOCKET *)nn;
+
     char local_address[INET6_ADDRSTRLEN];
     char remote_address[INET6_ADDRSTRLEN];
 
@@ -55,7 +57,9 @@ static void print_local_listeners(LS_STATE *ls __maybe_unused, LOCAL_SOCKET *n,
     printf("%s|%s|%u|%s\n", protocol_name(n), local_address, n->local.port, string2str(n->cmdline));
 }
 
-static void print_local_listeners_debug(LS_STATE *ls __maybe_unused, LOCAL_SOCKET *n, void *data __maybe_unused) {
+static void print_local_listeners_debug(LS_STATE *ls __maybe_unused, const LOCAL_SOCKET *nn, void *data __maybe_unused) {
+    LOCAL_SOCKET *n = (LOCAL_SOCKET *)nn;
+
     char local_address[INET6_ADDRSTRLEN];
     char remote_address[INET6_ADDRSTRLEN];
 
@@ -155,7 +159,7 @@ int main(int argc, char **argv) {
                     "\n"
                     " Current options:\n"
                     "\n"
-                    "    %s %s %s %s %s %s %s %s %s\n"
+                    "    %s %s %s %s %s %s %s %s %s %s %s %s\n"
                     "\n"
                     " Option 'debug' enables all sources and all directions and provides\n"
                     " a full dump of current sockets.\n"
@@ -163,6 +167,8 @@ int main(int argc, char **argv) {
                     " Option 'report' reports timings per step while collecting and processing\n"
                     " system information.\n"
                     "\n"
+                    " Option 'procfile' uses procfile to read proc files, instead of getline().\n"
+                    "\n"
                     " DIRECTION DETECTION\n"
                     " The program detects the direction of the sockets using these rules:\n"
                     "\n"
@@ -208,6 +214,9 @@ int main(int argc, char **argv) {
                     , ls.config.inbound ? "inbound" : "no-inbound"
                     , ls.config.outbound ? "outbound" : "no-outbound"
                     , ls.config.namespaces ? "namespaces" : "no-namespaces"
+                    , ls.config.no_mnl ? "no-mnl" : "mnl"
+                    , ls.config.procfile ? "procfile" : "no-procfile"
+                    , ls.config.report ? "report" : "no-report"
                     );
             exit(1);
         }
@@ -233,6 +242,7 @@ int main(int argc, char **argv) {
             ls.config.namespaces = true;
             ls.config.tcp_info = true;
             ls.config.uid = true;
+            ls.config.procfile = false;
             ls.config.max_errors = SIZE_MAX;
             ls.config.cb = print_local_listeners_debug;
 
@@ -294,6 +304,10 @@ int main(int argc, char **argv) {
             ls.config.no_mnl = !positive;
             // fprintf(stderr, "%s mnl\n", positive ? "enabling" : "disabling");
         }
+        else if (strcmp("procfile", s) == 0) {
+            ls.config.procfile = positive;
+            // fprintf(stderr, "%s procfile\n", positive ? "enabling" : "disabling");
+        }
         else if (strcmp("report", s) == 0) {
             ls.config.report = positive;
             // fprintf(stderr, "%s report\n", positive ? "enabling" : "disabling");
@@ -304,16 +318,21 @@ int main(int argc, char **argv) {
         }
     }
 
+#if defined(LOCAL_SOCKETS_USE_SETNS)
     SPAWN_SERVER *spawn_server = spawn_server_create(SPAWN_SERVER_OPTION_CALLBACK, NULL, local_sockets_spawn_server_callback, argc, (const char **)argv);
     if(spawn_server == NULL) {
         fprintf(stderr, "Cannot create spawn server.\n");
         exit(1);
     }
+
     ls.spawn_server = spawn_server;
+#endif
 
     local_sockets_process(&ls);
 
+#if defined(LOCAL_SOCKETS_USE_SETNS)
     spawn_server_destroy(spawn_server);
+#endif
 
     getrusage(RUSAGE_SELF, &ended);
 
@@ -345,6 +364,35 @@ int main(int argc, char **argv) {
 
         duration_snprintf(buf, sizeof(buf), (int64_t)total_ut, "us", true);
         fprintf(stderr, "%20s: %6.2f%% %s\n", "TOTAL", 100.0, buf);
+
+        fprintf(stderr, "\n");
+        fprintf(stderr, "Namespaces    [ found: %zu, absent: %zu, invalid: %zu ]\n"
+#if defined(LOCAL_SOCKETS_USE_SETNS)
+                        "  \\_    forks [ tried: %zu, failed: %zu, unresponsive: %zu ]\n"
+                        "  \\_  sockets [ new: %zu, existing: %zu ]\n"
+#endif
+                , ls.stats.namespaces_found, ls.stats.namespaces_absent, ls.stats.namespaces_invalid
+#if defined(LOCAL_SOCKETS_USE_SETNS)
+                , ls.stats.namespaces_forks_attempted, ls.stats.namespaces_forks_failed, ls.stats.namespaces_forks_unresponsive
+                , ls.stats.namespaces_sockets_new, ls.stats.namespaces_sockets_existing
+#endif
+                );
+
+        fprintf(stderr, "\n");
+        fprintf(stderr, "Sockets       [ found: %zu ]\n",
+                ls.stats.sockets_added);
+
+        fprintf(stderr, "\n");
+        fprintf(stderr, "Main Procfile [ opens: %zu, reads: %zu, resizes: %zu, memory: %zu ]\n"
+                        "  \\_    reads [ total bytes read: %zu, average read size: %zu, max read size: %zu ]\n"
+                        "  \\_      max [ max file size: %zu, max lines: %zu, max words: %zu ]\n",
+                ls.stats.ff.opens, ls.stats.ff.reads, ls.stats.ff.resizes, ls.stats.ff.memory,
+                ls.stats.ff.total_read_bytes, ls.stats.ff.total_read_bytes / (ls.stats.ff.reads ? ls.stats.ff.reads : 1), ls.stats.ff.max_read_size,
+                ls.stats.ff.max_source_bytes, ls.stats.ff.max_lines, ls.stats.ff.max_words);
+
+        fprintf(stderr, "\n");
+        fprintf(stderr, "MNL(without namespaces) [ requests: %zu ]\n",
+                ls.stats.mnl_sends);
     }
 
     return 0;

+ 1 - 0
src/libnetdata/aral/aral.c

@@ -799,6 +799,7 @@ ARAL *aral_create(const char *name, size_t element_size, size_t initial_page_ele
         aral_delete_leftover_files(ar->config.name, directory_name, file);
     }
 
+    errno_clear();
     internal_error(true,
                    "ARAL: '%s' "
                    "element size %zu (requested %zu bytes), "

+ 29 - 11
src/libnetdata/log/nd_log-init.c

@@ -265,17 +265,13 @@ void nd_log_reopen_log_files(bool log) {
     if(log)
         netdata_log_info("Reopening all log files.");
 
-    nd_log.std_output.initialized = false;
-    nd_log.std_error.initialized = false;
-    nd_log.journal_direct.initialized = false;
-    nd_log.journal.initialized = false;
     nd_log_initialize();
 
     if(log)
         netdata_log_info("Log files re-opened.");
 }
 
-void nd_log_reopen_log_files_for_spawn_server(void) {
+void nd_log_reopen_log_files_for_spawn_server(const char *name) {
     gettid_uncached();
 
     if(nd_log.syslog.initialized) {
@@ -291,11 +287,33 @@ void nd_log_reopen_log_files_for_spawn_server(void) {
         nd_log_journal_direct_init(NULL);
     }
 
-    nd_log.sources[NDLS_UNSET].method = NDLM_DISABLED;
-    nd_log.sources[NDLS_ACCESS].method = NDLM_DISABLED;
-    nd_log.sources[NDLS_ACLK].method = NDLM_DISABLED;
-    nd_log.sources[NDLS_DEBUG].method = NDLM_DISABLED;
-    nd_log.sources[NDLS_HEALTH].method = NDLM_DISABLED;
-    nd_log_reopen_log_files(false);
+    for(size_t i = 0; i < _NDLS_MAX ;i++) {
+        if(i != NDLS_COLLECTORS && i != NDLS_DAEMON) continue;
+
+        spinlock_init(&nd_log.sources[i].spinlock);
+        nd_log.sources[i].fd = -1;
+        nd_log.sources[i].fp = NULL;
+        nd_log.sources[i].pending_msg = NULL;
+#if defined(OS_WINDOWS)
+        nd_log.sources[i].hEventLog = NULL;
+#endif
+    }
+
+    for(size_t i = 0; i < _NDLS_MAX ;i++) {
+        if(i == NDLS_COLLECTORS || i == NDLS_DAEMON) continue;
+        nd_log.sources[i].method = NDLM_DISABLED;
+    }
+
+    spinlock_init(&nd_log.std_output.spinlock);
+    spinlock_init(&nd_log.std_error.spinlock);
+
+    nd_log.journal.initialized = false;
+    nd_log.journal_direct.initialized = false;
+    nd_log.syslog.initialized = false;
+    nd_log.eventlog.initialized = false;
+    nd_log.std_output.initialized = false;
+    nd_log.std_error.initialized = false;
+
+    nd_log_initialize_for_external_plugins(name);
 }
 

+ 1 - 1
src/libnetdata/log/nd_log.h

@@ -23,7 +23,7 @@ void chown_open_file(int fd, uid_t uid, gid_t gid);
 void nd_log_chown_log_files(uid_t uid, gid_t gid);
 void nd_log_set_flood_protection(size_t logs, time_t period);
 void nd_log_initialize_for_external_plugins(const char *name);
-void nd_log_reopen_log_files_for_spawn_server(void);
+void nd_log_reopen_log_files_for_spawn_server(const char *name);
 bool nd_log_journal_socket_available(void);
 ND_LOG_FIELD_ID nd_log_field_id_by_journal_name(const char *field, size_t len);
 int nd_log_priority2id(const char *priority);

+ 453 - 169
src/libnetdata/maps/local-sockets.h

@@ -9,7 +9,10 @@
 #define _countof(x) (sizeof(x) / sizeof(*(x)))
 #endif
 
-#ifdef HAVE_LIBMNL
+#define LOCAL_SOCKETS_USE_SETNS
+#define USE_LIBMNL_AFTER_SETNS
+
+#if defined(HAVE_LIBMNL)
 #include <linux/rtnetlink.h>
 #include <linux/inet_diag.h>
 #include <linux/sock_diag.h>
@@ -67,7 +70,7 @@ struct local_port;
 // --------------------------------------------------------------------------------------------------------------------
 
 struct local_socket_state;
-typedef void (*local_sockets_cb_t)(struct local_socket_state *state, struct local_socket *n, void *data);
+typedef void (*local_sockets_cb_t)(struct local_socket_state *state, const struct local_socket *n, void *data);
 
 struct local_sockets_config {
     bool listening;
@@ -85,6 +88,7 @@ struct local_sockets_config {
     bool namespaces;
     bool tcp_info;
     bool no_mnl;
+    bool procfile;
     bool report;
 
     size_t max_errors;
@@ -94,9 +98,12 @@ struct local_sockets_config {
     void *data;
 
     const char *host_prefix;
+};
 
-    // internal use
+struct local_sockets_state {
+    uint32_t nl_seq;
     uint64_t net_ns_inode;
+    pid_t net_ns_pid;
 };
 
 struct timing_work {
@@ -105,32 +112,54 @@ struct timing_work {
     const char *name;
 };
 
+struct local_sockets_ns_req {
+    struct local_sockets_config config;
+    struct local_sockets_state ns_state;
+};
+
 typedef struct local_socket_state {
     struct local_sockets_config config;
+    struct local_sockets_state ns_state;
 
     struct {
         size_t mnl_sends;
-        size_t namespaces_found;
         size_t tcp_info_received;
         size_t pid_fds_processed;
         size_t pid_fds_opendir_failed;
         size_t pid_fds_readlink_failed;
         size_t pid_fds_parse_failed;
         size_t errors_encountered;
+
+        size_t sockets_added;
+
+        size_t namespaces_found;
+        size_t namespaces_absent;
+        size_t namespaces_invalid;
+#if defined(LOCAL_SOCKETS_USE_SETNS)
+        size_t namespaces_forks_attempted;
+        size_t namespaces_forks_failed;
+        size_t namespaces_forks_unresponsive;
+        size_t namespaces_sockets_new;
+        size_t namespaces_sockets_existing;
+#endif
+
+        struct procfile_stats ff;
     } stats;
 
     size_t timings_idx;
-    struct timing_work timings[20];
+    struct timing_work timings[30];
 
+#if defined(LOCAL_SOCKETS_USE_SETNS)
     bool spawn_server_is_mine;
     SPAWN_SERVER *spawn_server;
+#endif
 
-#ifdef HAVE_LIBMNL
-    bool use_mnl;
-    struct mnl_socket *nl;
+#if defined(HAVE_LIBMNL)
     uint16_t tmp_protocol;
 #endif
 
+    procfile *ff;
+
     ARAL *local_socket_aral;
     ARAL *pid_socket_aral;
     SPINLOCK spinlock; // for namespaces
@@ -238,7 +267,9 @@ typedef struct local_socket {
 #endif
 } LOCAL_SOCKET;
 
+#if defined(LOCAL_SOCKETS_USE_SETNS)
 static inline int local_sockets_spawn_server_callback(SPAWN_REQUEST *request);
+#endif
 
 // --------------------------------------------------------------------------------------------------------------------
 
@@ -269,7 +300,7 @@ static bool local_sockets_is_ipv4_mapped_ipv6_address(const struct in6_addr *add
     return memcmp(addr->s6_addr, ipv4_mapped_prefix, 12) == 0;
 }
 
-static bool local_sockets_is_loopback_address(struct socket_endpoint *se) {
+static bool local_sockets_is_loopback_address(const struct socket_endpoint *se) {
     if (se->family == AF_INET) {
         // For IPv4, loopback addresses are in the 127.0.0.0/8 range
         return (ntohl(se->ip.ipv4) >> 24) == 127; // Check if the first byte is 127
@@ -303,7 +334,7 @@ static inline bool local_sockets_is_ipv4_reserved_address(uint32_t ip) {
             );
 }
 
-static inline bool local_sockets_is_private_address(struct socket_endpoint *se) {
+static inline bool local_sockets_is_private_address(const struct socket_endpoint *se) {
     if (se->family == AF_INET) {
         return local_sockets_is_ipv4_reserved_address(se->ip.ipv4);
     }
@@ -337,7 +368,7 @@ static inline bool local_sockets_is_private_address(struct socket_endpoint *se)
     return false;
 }
 
-static bool local_sockets_is_multicast_address(struct socket_endpoint *se) {
+static bool local_sockets_is_multicast_address(const struct socket_endpoint *se) {
     if (se->family == AF_INET) {
         // For IPv4, check if the address is 0.0.0.0
         uint32_t ip = htonl(se->ip.ipv4);
@@ -352,7 +383,7 @@ static bool local_sockets_is_multicast_address(struct socket_endpoint *se) {
     return false;
 }
 
-static bool local_sockets_is_zero_address(struct socket_endpoint *se) {
+static bool local_sockets_is_zero_address(const struct socket_endpoint *se) {
     if (se->family == AF_INET) {
         // For IPv4, check if the address is 0.0.0.0
         return se->ip.ipv4 == 0;
@@ -365,7 +396,7 @@ static bool local_sockets_is_zero_address(struct socket_endpoint *se) {
     return false;
 }
 
-static inline const char *local_sockets_address_space(struct socket_endpoint *se) {
+static inline const char *local_sockets_address_space(const struct socket_endpoint *se) {
     if(local_sockets_is_zero_address(se))
         return "zero";
     else if(local_sockets_is_loopback_address(se))
@@ -380,7 +411,7 @@ static inline const char *local_sockets_address_space(struct socket_endpoint *se
 
 // --------------------------------------------------------------------------------------------------------------------
 
-static inline bool is_local_socket_ipv46(LOCAL_SOCKET *n) {
+static inline bool is_local_socket_ipv46(const LOCAL_SOCKET *n) {
     return n->local.family == AF_INET6 &&
             n->direction == SOCKET_DIRECTION_LISTEN &&
             local_sockets_is_zero_address(&n->local) &&
@@ -615,6 +646,8 @@ static inline bool local_sockets_add_socket(LS_STATE *ls, LOCAL_SOCKET *tmp) {
         return false;
     }
 
+    ls->stats.sockets_added++;
+
     n = aral_mallocz(ls->local_socket_aral);
     *n = *tmp; // copy all contents
 
@@ -683,33 +716,7 @@ static inline bool local_sockets_add_socket(LS_STATE *ls, LOCAL_SOCKET *tmp) {
     return true;
 }
 
-#ifdef HAVE_LIBMNL
-
-static inline void local_sockets_libmnl_init(LS_STATE *ls) {
-    if(ls->config.no_mnl) return;
-
-    ls->nl = mnl_socket_open(NETLINK_INET_DIAG);
-    if (ls->nl == NULL) {
-        local_sockets_log(ls, "cannot open libmnl netlink socket");
-        ls->use_mnl = false;
-    }
-    else if (mnl_socket_bind(ls->nl, 0, MNL_SOCKET_AUTOPID) < 0) {
-        local_sockets_log(ls, "cannot bind libmnl netlink socket");
-        mnl_socket_close(ls->nl);
-        ls->nl = NULL;
-        ls->use_mnl = false;
-    }
-    else
-        ls->use_mnl = true;
-}
-
-static inline void local_sockets_libmnl_cleanup(LS_STATE *ls) {
-    if(ls->nl) {
-        mnl_socket_close(ls->nl);
-        ls->nl = NULL;
-        ls->use_mnl = false;
-    }
-}
+#if defined(HAVE_LIBMNL)
 
 static inline int local_sockets_libmnl_cb_data(const struct nlmsghdr *nlh, void *data) {
     LS_STATE *ls = data;
@@ -784,16 +791,30 @@ static inline int local_sockets_libmnl_cb_data(const struct nlmsghdr *nlh, void
 static inline bool local_sockets_libmnl_get_sockets(LS_STATE *ls, uint16_t family, uint16_t protocol) {
     ls->tmp_protocol = protocol;
 
-    char buf[MNL_SOCKET_BUFFER_SIZE];
-    struct nlmsghdr *nlh;
-    struct inet_diag_req_v2 req;
-    unsigned int seq, portid = mnl_socket_get_portid(ls->nl);
+    struct mnl_socket *nl = mnl_socket_open(NETLINK_INET_DIAG);
+    if (nl == NULL) {
+        local_sockets_log(ls, "mnl_socket_open() failed");
+        return false;
+    }
 
-    memset(&req, 0, sizeof(req));
-    req.sdiag_family = family;
-    req.sdiag_protocol = protocol;
-    req.idiag_states = -1;
-    req.idiag_ext = 0;
+    if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+        local_sockets_log(ls, "mnl_socket_bind() failed");
+        mnl_socket_close(nl);
+        return false;
+    }
+
+    char buf[MNL_SOCKET_BUFFER_SIZE];
+    struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf);
+    nlh->nlmsg_type = SOCK_DIAG_BY_FAMILY;
+    nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+    nlh->nlmsg_seq = ls->ns_state.nl_seq ? ls->ns_state.nl_seq++ : time(NULL);
+
+    struct inet_diag_req_v2 req = {
+        .sdiag_family = family,
+        .sdiag_protocol = protocol,
+        .idiag_states = ~0,  // Request all socket states
+        .idiag_ext = 0,
+    };
 
     if(family == AF_INET6)
         req.idiag_ext |= 1 << (INET_DIAG_SKV6ONLY - 1);
@@ -801,35 +822,106 @@ static inline bool local_sockets_libmnl_get_sockets(LS_STATE *ls, uint16_t famil
     if(protocol == IPPROTO_TCP && ls->config.tcp_info)
         req.idiag_ext |= 1 << (INET_DIAG_INFO - 1);
 
-    nlh = mnl_nlmsg_put_header(buf);
-    nlh->nlmsg_type = SOCK_DIAG_BY_FAMILY;
-    nlh->nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST;
-    nlh->nlmsg_seq = seq = time(NULL);
     mnl_nlmsg_put_extra_header(nlh, sizeof(req));
     memcpy(mnl_nlmsg_get_payload(nlh), &req, sizeof(req));
 
     ls->stats.mnl_sends++;
-    if (mnl_socket_sendto(ls->nl, nlh, nlh->nlmsg_len) < 0) {
-        local_sockets_log(ls, "mnl_socket_send failed");
+    if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
+        local_sockets_log(ls, "mnl_socket_sendto() failed");
+        mnl_socket_close(nl);
         return false;
     }
 
+    bool rc = true;
+    size_t received = 0;
     ssize_t ret;
-    while ((ret = mnl_socket_recvfrom(ls->nl, buf, sizeof(buf))) > 0) {
-        ret = mnl_cb_run(buf, ret, seq, portid, local_sockets_libmnl_cb_data, ls);
-        if (ret <= MNL_CB_STOP)
+    while ((ret = mnl_socket_recvfrom(nl, buf, sizeof(buf))) > 0) {
+        ret = mnl_cb_run(buf, ret, 0, 0, local_sockets_libmnl_cb_data, ls);
+        if (ret == MNL_CB_ERROR) {
+            local_sockets_log(ls, "mnl_cb_run() failed");
+            rc = false;
             break;
+        }
+        else if (ret <= MNL_CB_STOP)
+            break;
+
+        received++;
     }
+    mnl_socket_close(nl);
+
     if (ret == -1) {
-        local_sockets_log(ls, "mnl_socket_recvfrom");
+        local_sockets_log(ls, "mnl_socket_recvfrom() failed");
+        rc = false;
+    }
+
+    return rc;
+}
+#endif // HAVE_LIBMNL
+
+static inline bool local_sockets_process_proc_line(LS_STATE *ls, const char *filename, uint16_t family, uint16_t protocol, size_t line, char **words, size_t num_words) {
+    // char *sl_txt = get_word(words, num_words, 0);
+    char *local_ip_txt = get_word(words, num_words, 1);
+    char *local_port_txt = get_word(words, num_words, 2);
+    char *remote_ip_txt = get_word(words, num_words, 3);
+    char *remote_port_txt = get_word(words, num_words, 4);
+    char *state_txt = get_word(words, num_words, 5);
+    char *tx_queue_txt = get_word(words, num_words, 6);
+    char *rx_queue_txt = get_word(words, num_words, 7);
+    char *tr_txt = get_word(words, num_words, 8);
+    char *tm_when_txt = get_word(words, num_words, 9);
+    char *retrans_txt = get_word(words, num_words, 10);
+    char *uid_txt = get_word(words, num_words, 11);
+    // char *timeout_txt = get_word(words, num_words, 12);
+    char *inode_txt = get_word(words, num_words, 13);
+
+    if(!local_ip_txt || !local_port_txt || !remote_ip_txt || !remote_port_txt || !state_txt ||
+        !tx_queue_txt || !rx_queue_txt || !tr_txt || !tm_when_txt || !retrans_txt || !uid_txt || !inode_txt) {
+        local_sockets_log(ls, "cannot parse ipv4 line No %zu of filename '%s'", line, filename);
         return false;
     }
 
+    LOCAL_SOCKET n = {
+        .direction = SOCKET_DIRECTION_NONE,
+        .ipv6ony = {
+            .checked = false,
+            .ipv46 = false,
+        },
+        .local = {
+            .family = family,
+            .protocol = protocol,
+        },
+        .remote = {
+            .family = family,
+            .protocol = protocol,
+        },
+        .uid = UID_UNSET,
+    };
+
+    n.local.port = str2uint32_hex(local_port_txt, NULL);
+    n.remote.port = str2uint32_hex(remote_port_txt, NULL);
+    n.state = str2uint32_hex(state_txt, NULL);
+    n.wqueue = str2uint32_hex(tx_queue_txt, NULL);
+    n.rqueue = str2uint32_hex(rx_queue_txt, NULL);
+    n.timer = str2uint32_hex(tr_txt, NULL);
+    n.expires = str2uint32_hex(tm_when_txt, NULL);
+    n.retransmits = str2uint32_hex(retrans_txt, NULL);
+    n.uid = str2uint32_t(uid_txt, NULL);
+    n.inode = str2uint64_t(inode_txt, NULL);
+
+    if(family == AF_INET) {
+        n.local.ip.ipv4 = str2uint32_hex(local_ip_txt, NULL);
+        n.remote.ip.ipv4 = str2uint32_hex(remote_ip_txt, NULL);
+    }
+    else if(family == AF_INET6) {
+        ipv6_to_in6_addr(local_ip_txt, &n.local.ip.ipv6);
+        ipv6_to_in6_addr(remote_ip_txt, &n.remote.ip.ipv6);
+    }
+
+    local_sockets_add_socket(ls, &n);
     return true;
 }
-#endif // HAVE_LIBMNL
 
-static inline bool local_sockets_read_proc_net_x(LS_STATE *ls, const char *filename, uint16_t family, uint16_t protocol) {
+static inline bool local_sockets_read_proc_net_x_getline(LS_STATE *ls, const char *filename, uint16_t family, uint16_t protocol) {
     static bool is_space[256] = {
         [':'] = true,
         [' '] = true,
@@ -863,67 +955,9 @@ static inline bool local_sockets_read_proc_net_x(LS_STATE *ls, const char *filen
             continue;
         }
 
-        LOCAL_SOCKET n = {
-            .direction = SOCKET_DIRECTION_NONE,
-            .ipv6ony = {
-                .checked = false,
-                .ipv46 = false,
-            },
-            .local = {
-                .family = family,
-                .protocol = protocol,
-            },
-            .remote = {
-                .family = family,
-                .protocol = protocol,
-            },
-            .uid = UID_UNSET,
-        };
-
         char *words[32];
         size_t num_words = quoted_strings_splitter(line, words, 32, is_space);
-        // char *sl_txt = get_word(words, num_words, 0);
-        char *local_ip_txt = get_word(words, num_words, 1);
-        char *local_port_txt = get_word(words, num_words, 2);
-        char *remote_ip_txt = get_word(words, num_words, 3);
-        char *remote_port_txt = get_word(words, num_words, 4);
-        char *state_txt = get_word(words, num_words, 5);
-        char *tx_queue_txt = get_word(words, num_words, 6);
-        char *rx_queue_txt = get_word(words, num_words, 7);
-        char *tr_txt = get_word(words, num_words, 8);
-        char *tm_when_txt = get_word(words, num_words, 9);
-        char *retrans_txt = get_word(words, num_words, 10);
-        char *uid_txt = get_word(words, num_words, 11);
-        // char *timeout_txt = get_word(words, num_words, 12);
-        char *inode_txt = get_word(words, num_words, 13);
-
-        if(!local_ip_txt || !local_port_txt || !remote_ip_txt || !remote_port_txt || !state_txt ||
-            !tx_queue_txt || !rx_queue_txt || !tr_txt || !tm_when_txt || !retrans_txt || !uid_txt || !inode_txt) {
-            local_sockets_log(ls, "cannot parse ipv4 line No %zu of filename '%s'", counter, filename);
-            continue;
-        }
-
-        n.local.port = str2uint32_hex(local_port_txt, NULL);
-        n.remote.port = str2uint32_hex(remote_port_txt, NULL);
-        n.state = str2uint32_hex(state_txt, NULL);
-        n.wqueue = str2uint32_hex(tx_queue_txt, NULL);
-        n.rqueue = str2uint32_hex(rx_queue_txt, NULL);
-        n.timer = str2uint32_hex(tr_txt, NULL);
-        n.expires = str2uint32_hex(tm_when_txt, NULL);
-        n.retransmits = str2uint32_hex(retrans_txt, NULL);
-        n.uid = str2uint32_t(uid_txt, NULL);
-        n.inode = str2uint64_t(inode_txt, NULL);
-
-        if(family == AF_INET) {
-            n.local.ip.ipv4 = str2uint32_hex(local_ip_txt, NULL);
-            n.remote.ip.ipv4 = str2uint32_hex(remote_ip_txt, NULL);
-        }
-        else if(family == AF_INET6) {
-            ipv6_to_in6_addr(local_ip_txt, &n.local.ip.ipv6);
-            ipv6_to_in6_addr(remote_ip_txt, &n.remote.ip.ipv6);
-        }
-
-        local_sockets_add_socket(ls, &n);
+        local_sockets_process_proc_line(ls, filename, family, protocol, counter, words, num_words);
     }
 
     fclose(fp);
@@ -934,6 +968,59 @@ static inline bool local_sockets_read_proc_net_x(LS_STATE *ls, const char *filen
     return true;
 }
 
+#define INITIALLY_EXPECTED_PROC_NET_LINES 16384
+#define PROC_NET_BYTES_PER_LINE 155 // 105 for IPv4, 155 for IPv6
+#define PROC_NET_WORDS_PER_LINE 22
+#define INITIALLY_EXPECTED_PROC_NET_WORDS (INITIALLY_EXPECTED_PROC_NET_LINES * PROC_NET_WORDS_PER_LINE)
+#define INITIALLY_EXPECTED_PROC_NET_BYTES (INITIALLY_EXPECTED_PROC_NET_LINES * PROC_NET_BYTES_PER_LINE)
+
+static inline bool local_sockets_read_proc_net_x_procfile(LS_STATE *ls, const char *filename, uint16_t family, uint16_t protocol) {
+    if(family != AF_INET && family != AF_INET6)
+        return false;
+
+    procfile_set_adaptive_allocation(true, INITIALLY_EXPECTED_PROC_NET_BYTES, INITIALLY_EXPECTED_PROC_NET_LINES, INITIALLY_EXPECTED_PROC_NET_WORDS);
+
+    bool copy_initial_ff_stats = ls->ff == NULL && ls->stats.ff.memory > 0;
+    ls->ff = procfile_reopen(ls->ff, filename, ls->ff ? NULL :" :", PROCFILE_FLAG_DEFAULT);
+
+    // we just created ff, copy our old stats to it
+    if(ls->ff && copy_initial_ff_stats) ls->ff->stats = ls->stats.ff;
+
+    ls->ff = procfile_readall(ls->ff);
+    if(!ls->ff) return false;
+
+    // get the latest stats from ff;
+    ls->stats.ff = ls->ff->stats;
+
+    for(size_t l = 1; l < procfile_lines(ls->ff) ;l++) {
+        size_t w = procfile_linewords(ls->ff, l);
+        if(!w) continue;
+        if(w < 14) {
+            local_sockets_log(ls, "too small line No %zu of filename '%s' (has %zu words)", l, filename, w);
+            continue;
+        }
+
+        char *words[14] = { 0 };
+        words[0] = procfile_lineword(ls->ff, l, 0);
+        words[1] = procfile_lineword(ls->ff, l, 1);
+        words[2] = procfile_lineword(ls->ff, l, 2);
+        words[3] = procfile_lineword(ls->ff, l, 3);
+        words[4] = procfile_lineword(ls->ff, l, 4);
+        words[5] = procfile_lineword(ls->ff, l, 5);
+        words[6] = procfile_lineword(ls->ff, l, 6);
+        words[7] = procfile_lineword(ls->ff, l, 7);
+        words[8] = procfile_lineword(ls->ff, l, 8);
+        words[9] = procfile_lineword(ls->ff, l, 9);
+        words[10] = procfile_lineword(ls->ff, l, 10);
+        words[11] = procfile_lineword(ls->ff, l, 11);
+        words[12] = procfile_lineword(ls->ff, l, 12);
+        words[13] = procfile_lineword(ls->ff, l, 13);
+        local_sockets_process_proc_line(ls, filename, family, protocol, l, words, _countof(words));
+    }
+
+    return true;
+}
+
 // --------------------------------------------------------------------------------------------------------------------
 
 static inline void local_sockets_detect_directions(LS_STATE *ls) {
@@ -1025,31 +1112,33 @@ static inline void local_sockets_init(LS_STATE *ls) {
 
     memset(&ls->stats, 0, sizeof(ls->stats));
 
-#ifdef HAVE_LIBMNL
-    ls->use_mnl = false;
-    ls->nl = NULL;
+#if defined(HAVE_LIBMNL)
     ls->tmp_protocol = 0;
-    local_sockets_libmnl_init(ls);
 #endif
 
+#if defined(LOCAL_SOCKETS_USE_SETNS)
     if(ls->config.namespaces && ls->spawn_server == NULL) {
         ls->spawn_server = spawn_server_create(SPAWN_SERVER_OPTION_CALLBACK, NULL, local_sockets_spawn_server_callback, 0, NULL);
         ls->spawn_server_is_mine = true;
     }
     else
         ls->spawn_server_is_mine = false;
+#endif
 }
 
 static inline void local_sockets_cleanup(LS_STATE *ls) {
+    if(ls->ff) {
+        ls->stats.ff = ls->ff->stats;
+        procfile_close(ls->ff);
+        ls->ff = NULL;
+    }
 
+#if defined(LOCAL_SOCKETS_USE_SETNS)
     if(ls->spawn_server_is_mine) {
         spawn_server_destroy(ls->spawn_server);
         ls->spawn_server = NULL;
         ls->spawn_server_is_mine = false;
     }
-
-#ifdef HAVE_LIBMNL
-    local_sockets_libmnl_cleanup(ls);
 #endif
 
     // free the sockets hashtable data
@@ -1087,19 +1176,6 @@ static inline void local_sockets_cleanup(LS_STATE *ls) {
 
 // --------------------------------------------------------------------------------------------------------------------
 
-static inline void local_sockets_do_family_protocol(LS_STATE *ls, const char *filename, uint16_t family, uint16_t protocol) {
-#ifdef HAVE_LIBMNL
-    if(!ls->config.no_mnl && ls->nl && ls->use_mnl) {
-        ls->use_mnl = local_sockets_libmnl_get_sockets(ls, family, protocol);
-
-        if(ls->use_mnl)
-            return;
-    }
-#endif
-
-    local_sockets_read_proc_net_x(ls, filename, family, protocol);
-}
-
 static inline void local_sockets_track_time(LS_STATE *ls, const char *name) {
     if(!ls->config.report || ls->timings_idx >= _countof(ls->timings))
         return;
@@ -1122,6 +1198,60 @@ static inline void local_sockets_track_time(LS_STATE *ls, const char *name) {
     }
 }
 
+static void local_sockets_track_time_by_protocol(LS_STATE *ls, bool mnl, uint16_t family, uint16_t protocol) {
+    if(mnl) {
+        if(family == AF_INET) {
+            if(protocol == IPPROTO_TCP)
+                local_sockets_track_time(ls, "mnl_read_tcp4");
+            else if(protocol == IPPROTO_UDP)
+                local_sockets_track_time(ls, "mnl_read_udp4");
+        }
+        else if(family == AF_INET6) {
+            if(protocol == IPPROTO_TCP)
+                local_sockets_track_time(ls, "mnl_read_tcp6");
+            else if(protocol == IPPROTO_UDP)
+                local_sockets_track_time(ls, "mnl_read_udp6");
+        }
+        else
+            local_sockets_track_time(ls, "mnl_read_unknown");
+    }
+    else {
+        if(family == AF_INET) {
+            if(protocol == IPPROTO_TCP)
+                local_sockets_track_time(ls, "proc_read_tcp4");
+            else if(protocol == IPPROTO_UDP)
+                local_sockets_track_time(ls, "proc_read_udp4");
+        }
+        else if(family == AF_INET6) {
+            if(protocol == IPPROTO_TCP)
+                local_sockets_track_time(ls, "proc_read_tcp6");
+            else if(protocol == IPPROTO_UDP)
+                local_sockets_track_time(ls, "proc_read_udp6");
+        }
+        else
+            local_sockets_track_time(ls, "proc_read_unknown");
+    }
+}
+
+static inline void local_sockets_do_family_protocol(LS_STATE *ls, const char *filename, uint16_t family, uint16_t protocol) {
+#if defined(HAVE_LIBMNL)
+    if(!ls->config.no_mnl) {
+        local_sockets_track_time_by_protocol(ls, true, family, protocol);
+        if(local_sockets_libmnl_get_sockets(ls, family, protocol))
+            return;
+
+        // else, do proc
+    }
+#endif
+
+    local_sockets_track_time_by_protocol(ls, false, family, protocol);
+
+    if(ls->config.procfile)
+        local_sockets_read_proc_net_x_procfile(ls, filename, family, protocol);
+    else
+        local_sockets_read_proc_net_x_getline(ls, filename, family, protocol);
+}
+
 static inline void local_sockets_read_all_system_sockets(LS_STATE *ls) {
     char path[FILENAME_MAX + 1];
 
@@ -1129,55 +1259,71 @@ static inline void local_sockets_read_all_system_sockets(LS_STATE *ls) {
         local_sockets_track_time(ls, "read_namespaces");
         snprintfz(path, sizeof(path), "%s/proc/self/ns/net", ls->config.host_prefix);
         local_sockets_read_proc_inode_link(ls, path, &ls->proc_self_net_ns_inode, "net");
-
     }
 
     if(ls->config.cmdline || ls->config.comm || ls->config.pid || ls->config.namespaces) {
-        local_sockets_track_time(ls, "read_proc_pids");
+        local_sockets_track_time(ls, "proc_read_pids");
         snprintfz(path, sizeof(path), "%s/proc", ls->config.host_prefix);
         local_sockets_find_all_sockets_in_proc(ls, path);
     }
 
     if(ls->config.tcp4) {
-        local_sockets_track_time(ls, "read_tcp4");
         snprintfz(path, sizeof(path), "%s/proc/net/tcp", ls->config.host_prefix);
         local_sockets_do_family_protocol(ls, path, AF_INET, IPPROTO_TCP);
     }
 
     if(ls->config.udp4) {
-        local_sockets_track_time(ls, "read_udp4");
         snprintfz(path, sizeof(path), "%s/proc/net/udp", ls->config.host_prefix);
         local_sockets_do_family_protocol(ls, path, AF_INET, IPPROTO_UDP);
     }
 
     if(ls->config.tcp6) {
-        local_sockets_track_time(ls, "read_tcp6");
         snprintfz(path, sizeof(path), "%s/proc/net/tcp6", ls->config.host_prefix);
         local_sockets_do_family_protocol(ls, path, AF_INET6, IPPROTO_TCP);
     }
 
     if(ls->config.udp6) {
-        local_sockets_track_time(ls, "read_udp6");
         snprintfz(path, sizeof(path), "%s/proc/net/udp6", ls->config.host_prefix);
         local_sockets_do_family_protocol(ls, path, AF_INET6, IPPROTO_UDP);
     }
 }
 
 // --------------------------------------------------------------------------------------------------------------------
+// switch namespaces to read namespace sockets
+
+#if defined(LOCAL_SOCKETS_USE_SETNS)
 
 struct local_sockets_child_work {
     int fd;
     uint64_t net_ns_inode;
 };
 
-static inline void local_sockets_send_to_parent(struct local_socket_state *ls __maybe_unused, struct local_socket *n, void *data) {
+#define LOCAL_SOCKET_TERMINATOR (struct local_socket) { \
+    .expires = UINT32_MAX,                              \
+    .timer = UINT8_MAX,                                 \
+    .inode = UINT64_MAX,                                \
+    .net_ns_inode = UINT64_MAX,                         \
+}
+
+static inline bool local_socket_is_terminator(const struct local_socket *n) {
+    static const struct local_socket t = LOCAL_SOCKET_TERMINATOR;
+    return (n->expires == t.expires &&
+            n->timer == t.timer &&
+            n->inode == t.inode &&
+            n->net_ns_inode == t.net_ns_inode);
+}
+
+static inline void local_sockets_send_to_parent(struct local_socket_state *ls, const struct local_socket *n, void *data) {
     struct local_sockets_child_work *cw = data;
     int fd = cw->fd;
 
-    if(n->net_ns_inode != cw->net_ns_inode)
-        return;
-
-    // local_sockets_log(ls, "child is sending inode %"PRIu64" of namespace %"PRIu64, n->inode, n->net_ns_inode);
+    if(!local_socket_is_terminator(n)) {
+        ls->stats.errors_encountered = 0;
+//        local_sockets_log(
+//            ls,
+//            "child is sending inode %"PRIu64" of namespace %"PRIu64", from namespace %"PRIu64" for pid %d",
+//            n->inode, n->net_ns_inode, ls->proc_self_net_ns_inode, ls->ns_state.net_ns_pid);
+    }
 
     if(write(fd, n, sizeof(*n)) != sizeof(*n))
         local_sockets_log(ls, "failed to write local socket to pipe");
@@ -1192,8 +1338,14 @@ static inline void local_sockets_send_to_parent(struct local_socket_state *ls __
 }
 
 static inline int local_sockets_spawn_server_callback(SPAWN_REQUEST *request) {
+    static const struct local_socket terminator = LOCAL_SOCKET_TERMINATOR;
+
+    struct local_sockets_ns_req *req = (struct local_sockets_ns_req *)request->data;
+
     LS_STATE ls = { 0 };
-    ls.config = *((struct local_sockets_config *)request->data);
+    ls.config = req->config;
+    ls.ns_state = req->ns_state;
+    ls.ns_state.nl_seq += gettid_uncached() * 10;
 
     // we don't need these inside namespaces
     ls.config.cmdline = false;
@@ -1201,9 +1353,13 @@ static inline int local_sockets_spawn_server_callback(SPAWN_REQUEST *request) {
     ls.config.pid = false;
     ls.config.namespaces = false;
 
+#if !defined(USE_LIBMNL_AFTER_SETNS)
+    ls.config.no_mnl = true; // disable mnl since this collects all sockets from the entire system
+#endif
+
     // initialize local sockets
     local_sockets_init(&ls);
-
+    ls.proc_self_net_ns_inode = ls.ns_state.net_ns_inode;
     ls.config.host_prefix = ""; // we need the /proc of the container
 
     struct local_sockets_child_work cw = {
@@ -1213,7 +1369,6 @@ static inline int local_sockets_spawn_server_callback(SPAWN_REQUEST *request) {
 
     ls.config.cb = local_sockets_send_to_parent;
     ls.config.data = &cw;
-    ls.proc_self_net_ns_inode = ls.config.net_ns_inode;
 
     // switch namespace using the custom fd passed via the spawn server
     if (setns(request->fds[3], CLONE_NEWNET) == -1) {
@@ -1221,6 +1376,9 @@ static inline int local_sockets_spawn_server_callback(SPAWN_REQUEST *request) {
         return EXIT_FAILURE;
     }
 
+    // close the custom fd
+    close(request->fds[3]); request->fds[3] = -1;
+
     // read all sockets from /proc
     local_sockets_read_all_system_sockets(&ls);
 
@@ -1228,10 +1386,7 @@ static inline int local_sockets_spawn_server_callback(SPAWN_REQUEST *request) {
     local_sockets_foreach_local_socket_call_cb(&ls);
 
     // send the terminating socket
-    struct local_socket zero = {
-        .net_ns_inode = ls.config.net_ns_inode,
-    };
-    local_sockets_send_to_parent(&ls, &zero, &cw);
+    local_sockets_send_to_parent(&ls, &terminator, &cw);
 
     local_sockets_cleanup(&ls);
 
@@ -1246,6 +1401,8 @@ static inline bool local_sockets_get_namespace_sockets_with_pid(LS_STATE *ls, st
     int fd = open(filename, O_RDONLY | O_CLOEXEC);
     if (fd == -1) {
         local_sockets_log(ls, "cannot open file '%s'", filename);
+        if(ls->config.report)
+            __atomic_add_fetch(&ls->stats.namespaces_absent, 1, __ATOMIC_RELAXED);
         return false;
     }
 
@@ -1253,28 +1410,46 @@ static inline bool local_sockets_get_namespace_sockets_with_pid(LS_STATE *ls, st
     if (fstat(fd, &statbuf) == -1) {
         close(fd);
         local_sockets_log(ls, "failed to get file statistics for '%s'", filename);
+        if(ls->config.report)
+            __atomic_add_fetch(&ls->stats.namespaces_absent, 1, __ATOMIC_RELAXED);
         return false;
     }
 
     if (statbuf.st_ino != ps->net_ns_inode) {
         close(fd);
         local_sockets_log(ls, "pid %d is not in the wanted network namespace", ps->pid);
+        if(ls->config.report)
+            __atomic_add_fetch(&ls->stats.namespaces_invalid, 1, __ATOMIC_RELAXED);
         return false;
     }
 
     if(ls->spawn_server == NULL) {
         close(fd);
         local_sockets_log(ls, "spawn server is not available");
+        if(ls->config.report)
+            __atomic_add_fetch(&ls->stats.namespaces_forks_failed, 1, __ATOMIC_RELAXED);
         return false;
     }
 
-    struct local_sockets_config config = ls->config;
-    config.net_ns_inode = ps->net_ns_inode;
-    SPAWN_INSTANCE *si = spawn_server_exec(ls->spawn_server, STDERR_FILENO, fd, NULL, &config, sizeof(config), SPAWN_INSTANCE_TYPE_CALLBACK);
+    struct local_sockets_ns_req req = {
+        .config = ls->config,
+        .ns_state = ls->ns_state,
+    };
+    req.ns_state.net_ns_pid = ps->pid;
+    req.ns_state.net_ns_inode = ps->net_ns_inode;
+
+    SPAWN_INSTANCE *si = spawn_server_exec(ls->spawn_server, STDERR_FILENO, fd, NULL, &req, sizeof(req), SPAWN_INSTANCE_TYPE_CALLBACK);
     close(fd); fd = -1;
 
+    if(ls->config.report)
+        __atomic_add_fetch(&ls->stats.namespaces_forks_attempted, 1, __ATOMIC_RELAXED);
+
     if(si == NULL) {
         local_sockets_log(ls, "cannot create spawn instance");
+
+        if(ls->config.report)
+            __atomic_add_fetch(&ls->stats.namespaces_forks_failed, 1, __ATOMIC_RELAXED);
+
         return false;
     }
 
@@ -1299,13 +1474,12 @@ static inline bool local_sockets_get_namespace_sockets_with_pid(LS_STATE *ls, st
 
         received++;
 
-        struct local_socket zero = {
-            .net_ns_inode = ps->net_ns_inode,
-        };
-        if(memcmp(&buf, &zero, sizeof(buf)) == 0) {
-            // the terminator
+        if(local_socket_is_terminator(&buf))
+            // the child finished
             break;
-        }
+
+        // overwrite the net_ns_inode we receive
+        buf.net_ns_inode = ps->net_ns_inode;
 
         spinlock_lock(&ls->spinlock);
 
@@ -1313,9 +1487,13 @@ static inline bool local_sockets_get_namespace_sockets_with_pid(LS_STATE *ls, st
         LOCAL_SOCKET *n = SIMPLE_HASHTABLE_SLOT_DATA(sl);
         if(n) {
             string_freez(buf.cmdline);
+
 //            local_sockets_log(ls,
 //                              "ns inode %" PRIu64" (comm: '%s', pid: %u, ns: %"PRIu64") already exists in hashtable (comm: '%s', pid: %u, ns: %"PRIu64") - ignoring duplicate",
 //                              buf.inode, buf.comm, buf.pid, buf.net_ns_inode, n->comm, n->pid, n->net_ns_inode);
+
+            if(ls->config.report)
+                __atomic_add_fetch(&ls->stats.namespaces_sockets_existing, 1, __ATOMIC_RELAXED);
         }
         else {
             n = aral_mallocz(ls->local_socket_aral);
@@ -1323,12 +1501,19 @@ static inline bool local_sockets_get_namespace_sockets_with_pid(LS_STATE *ls, st
             simple_hashtable_set_slot_LOCAL_SOCKET(&ls->sockets_hashtable, sl, n->inode, n);
 
             local_sockets_index_listening_port(ls, n);
+
+            if(ls->config.report)
+                __atomic_add_fetch(&ls->stats.namespaces_sockets_new, 1, __ATOMIC_RELAXED);
         }
 
         spinlock_unlock(&ls->spinlock);
     }
 
     spawn_server_exec_kill(ls->spawn_server, si);
+
+    if(ls->config.report && received == 0)
+        __atomic_add_fetch(&ls->stats.namespaces_forks_unresponsive, 1, __ATOMIC_RELAXED);
+
     return received > 0;
 }
 
@@ -1385,6 +1570,7 @@ static inline void local_sockets_namespaces(LS_STATE *ls) {
          const uint64_t inode = (uint64_t)SIMPLE_HASHTABLE_SLOT_DATA(sl);
 
         if(inode == ls->proc_self_net_ns_inode)
+            // skip our own namespace, we already have them
             continue;
 
         spinlock_unlock(&ls->spinlock);
@@ -1421,6 +1607,100 @@ static inline void local_sockets_namespaces(LS_STATE *ls) {
     }
 }
 
+#endif // LOCAL_SOCKETS_USE_SETNS
+
+// --------------------------------------------------------------------------------------------------------------------
+// read namespace sockets from the host's /proc
+
+#if !defined(LOCAL_SOCKETS_USE_SETNS)
+
+static inline bool local_sockets_namespaces_from_proc_with_pid(LS_STATE *ls, struct pid_socket *ps) {
+    char filename[1024];
+    snprintfz(filename, sizeof(filename), "%s/proc/%d/ns/net", ls->config.host_prefix, ps->pid);
+
+    // verify the pid is in the target namespace
+    int fd = open(filename, O_RDONLY | O_CLOEXEC);
+    if (fd == -1) {
+        local_sockets_log(ls, "cannot open file '%s'", filename);
+        if(ls->config.report)
+            __atomic_add_fetch(&ls->stats.namespaces_absent, 1, __ATOMIC_RELAXED);
+        return false;
+    }
+
+    struct stat statbuf;
+    if (fstat(fd, &statbuf) == -1) {
+        close(fd);
+        local_sockets_log(ls, "failed to get file statistics for '%s'", filename);
+        if(ls->config.report)
+            __atomic_add_fetch(&ls->stats.namespaces_absent, 1, __ATOMIC_RELAXED);
+        return false;
+    }
+
+    if (statbuf.st_ino != ps->net_ns_inode) {
+        close(fd);
+        local_sockets_log(ls, "pid %d is not in the wanted network namespace", ps->pid);
+        if(ls->config.report)
+            __atomic_add_fetch(&ls->stats.namespaces_invalid, 1, __ATOMIC_RELAXED);
+        return false;
+    }
+
+    char path[FILENAME_MAX + 1];
+
+    if(ls->config.tcp4) {
+        snprintfz(path, sizeof(path), "%s/proc/%d/net/tcp", ls->config.host_prefix, ps->pid);
+        if(!local_sockets_read_proc_net_x(ls, path, AF_INET, IPPROTO_TCP))
+            return false;
+    }
+
+    if(ls->config.udp4) {
+        snprintfz(path, sizeof(path), "%s/proc/%d/net/udp", ls->config.host_prefix, ps->pid);
+        if(!local_sockets_read_proc_net_x(ls, path, AF_INET, IPPROTO_UDP))
+            return false;
+    }
+
+    if(ls->config.tcp6) {
+        snprintfz(path, sizeof(path), "%s/proc/%d/net/tcp6", ls->config.host_prefix, ps->pid);
+        if(!local_sockets_read_proc_net_x(ls, path, AF_INET6, IPPROTO_TCP))
+            return false;
+    }
+
+    if(ls->config.udp6) {
+        snprintfz(path, sizeof(path), "%s/proc/%d/net/udp6", ls->config.host_prefix, ps->pid);
+        if(!local_sockets_read_proc_net_x(ls, path, AF_INET6, IPPROTO_UDP))
+            return false;
+    }
+
+    return true;
+}
+
+static inline void local_sockets_namespaces_from_proc(LS_STATE *ls) {
+    for(SIMPLE_HASHTABLE_SLOT_NET_NS *sl = simple_hashtable_first_read_only_NET_NS(&ls->ns_hashtable);
+         sl;
+         sl = simple_hashtable_next_read_only_NET_NS(&ls->ns_hashtable, sl)) {
+        const uint64_t inode = (uint64_t)SIMPLE_HASHTABLE_SLOT_DATA(sl);
+
+        if (inode == ls->proc_self_net_ns_inode)
+            // skip our own namespace, we already have them
+            continue;
+
+        ls->stats.namespaces_found++;
+
+        for(SIMPLE_HASHTABLE_SLOT_PID_SOCKET *sl_pid = simple_hashtable_first_read_only_PID_SOCKET(&ls->pid_sockets_hashtable) ;
+             sl_pid ;
+             sl_pid = simple_hashtable_next_read_only_PID_SOCKET(&ls->pid_sockets_hashtable, sl_pid)) {
+            struct pid_socket *ps = SIMPLE_HASHTABLE_SLOT_DATA(sl_pid);
+            if(!ps || ps->net_ns_inode != inode) continue;
+
+            // now we have a pid that has the same namespace inode
+
+            if(local_sockets_namespaces_from_proc_with_pid(ls, ps))
+                break;
+        }
+    }
+}
+
+#endif
+
 // --------------------------------------------------------------------------------------------------------------------
 
 static inline void local_sockets_process(LS_STATE *ls) {
@@ -1438,7 +1718,11 @@ static inline void local_sockets_process(LS_STATE *ls) {
     // check all socket namespaces
     if(ls->config.namespaces) {
         local_sockets_track_time(ls, "switch_namespaces");
+#if defined(LOCAL_SOCKETS_USE_SETNS)
         local_sockets_namespaces(ls);
+#else
+        local_sockets_namespaces_from_proc(ls);
+#endif
     }
 
     // detect the directions of the sockets
@@ -1456,7 +1740,7 @@ static inline void local_sockets_process(LS_STATE *ls) {
     local_sockets_cleanup(ls);
 }
 
-static inline void ipv6_address_to_txt(struct in6_addr *in6_addr, char *dst) {
+static inline void ipv6_address_to_txt(const struct in6_addr *in6_addr, char *dst) {
     struct sockaddr_in6 sa = { 0 };
 
     sa.sin6_family = AF_INET6;

+ 51 - 10
src/libnetdata/procfile/procfile.c

@@ -10,14 +10,23 @@
 
 int procfile_open_flags = O_RDONLY | O_CLOEXEC;
 
-int procfile_adaptive_initial_allocation = 0;
-
 // if adaptive allocation is set, these store the
 // max values we have seen so far
-size_t procfile_max_lines = PFLINES_INCREASE_STEP;
-size_t procfile_max_words = PFWORDS_INCREASE_STEP;
-size_t procfile_max_allocation = PROCFILE_INCREMENT_BUFFER;
-
+static bool procfile_adaptive_initial_allocation = false;
+static size_t procfile_max_lines = PFLINES_INCREASE_STEP;
+static size_t procfile_max_words = PFWORDS_INCREASE_STEP;
+static size_t procfile_max_allocation = PROCFILE_INCREMENT_BUFFER;
+
+void procfile_set_adaptive_allocation(bool enable, size_t bytes, size_t lines, size_t words) {
+    procfile_adaptive_initial_allocation = enable;
+
+    if(bytes > procfile_max_allocation)
+        procfile_max_allocation = bytes;
+    if(lines > procfile_max_lines)
+        procfile_max_lines = lines;
+    if(words > procfile_max_words)
+        procfile_max_words = words;
+}
 
 // ----------------------------------------------------------------------------
 
@@ -59,6 +68,8 @@ static inline void procfile_words_add(procfile *ff, char *str) {
 
         ff->words = fw = reallocz(fw, sizeof(pfwords) + (fw->size + wanted) * sizeof(char *));
         fw->size += wanted;
+        ff->stats.memory += wanted * sizeof(char *);
+        ff->stats.resizes++;
     }
 
     fw->words[fw->len++] = str;
@@ -92,7 +103,7 @@ static inline void procfile_words_free(pfwords *fw) {
 // An array of lines
 
 NEVERNULL
-static inline size_t *procfile_lines_add(procfile *ff) {
+static inline uint32_t *procfile_lines_add(procfile *ff) {
     // netdata_log_debug(D_PROCFILE, PF_PREFIX ":   adding line %d at word %d", fl->len, first_word);
 
     pflines *fl = ff->lines;
@@ -104,6 +115,8 @@ static inline size_t *procfile_lines_add(procfile *ff) {
 
         ff->lines = fl = reallocz(fl, sizeof(pflines) + (fl->size + wanted) * sizeof(ffline));
         fl->size += wanted;
+        ff->stats.memory += wanted * sizeof(ffline);
+        ff->stats.resizes++;
     }
 
     ffline *ffl = &fl->lines[fl->len++];
@@ -168,7 +181,7 @@ static void procfile_parser(procfile *ff) {
     char quote = 0;                     // the quote character - only when in quoted string
     size_t opened = 0;                  // counts the number of open parenthesis
 
-    size_t *line_words = procfile_lines_add(ff);
+    uint32_t *line_words = procfile_lines_add(ff);
 
     while(s < e) {
         PF_CHAR_TYPE ct = separators[(unsigned char)(*s)];
@@ -279,6 +292,8 @@ static void procfile_parser(procfile *ff) {
 }
 
 procfile *procfile_readall(procfile *ff) {
+    if(!ff) return NULL;
+
     // netdata_log_debug(D_PROCFILE, PF_PREFIX ": Reading file '%s'.", ff->filename);
 
     ff->len = 0;    // zero the used size
@@ -295,9 +310,12 @@ procfile *procfile_readall(procfile *ff) {
             netdata_log_debug(D_PROCFILE, PF_PREFIX ": Expanding data buffer for file '%s' by %zu bytes.", procfile_filename(ff), wanted);
             ff = reallocz(ff, sizeof(procfile) + ff->size + wanted);
             ff->size += wanted;
+            ff->stats.memory += wanted;
+            ff->stats.resizes++;
         }
 
-        netdata_log_debug(D_PROCFILE, "Reading file '%s', from position %zd with length %zd", procfile_filename(ff), s, (ssize_t)(ff->size - s));
+        // netdata_log_info("Reading file '%s', from position %zd with length %zd", procfile_filename(ff), s, (ssize_t)(ff->size - s));
+        ff->stats.reads++;
         r = read(ff->fd, &ff->data[s], ff->size - s);
         if(unlikely(r == -1)) {
             if(unlikely(!(ff->flags & PROCFILE_FLAG_NO_ERROR_ON_FILE_IO))) collector_error(PF_PREFIX ": Cannot read from file '%s' on fd %d", procfile_filename(ff), ff->fd);
@@ -307,6 +325,9 @@ procfile *procfile_readall(procfile *ff) {
             return NULL;
         }
 
+        if((ssize_t)ff->stats.max_read_size < r)
+            ff->stats.max_read_size = r;
+
         ff->len += r;
     }
 
@@ -329,6 +350,17 @@ procfile *procfile_readall(procfile *ff) {
         if(unlikely(ff->words->len > procfile_max_words)) procfile_max_words = ff->words->len;
     }
 
+    if(ff->stats.max_source_bytes < ff->len)
+        ff->stats.max_source_bytes = ff->len;
+
+    if(ff->stats.max_lines < ff->lines->len)
+        ff->stats.max_lines = ff->lines->len;
+
+    if(ff->stats.max_words < ff->words->len)
+        ff->stats.max_words = ff->words->len;
+
+    ff->stats.total_read_bytes += ff->len;
+
     // netdata_log_debug(D_PROCFILE, "File '%s' updated.", ff->filename);
     return ff;
 }
@@ -433,10 +465,18 @@ procfile *procfile_open(const char *filename, const char *separators, uint32_t f
     ff->size = size;
     ff->len = 0;
     ff->flags = flags;
+    ff->stats.opens = 1;
+    ff->stats.reads = ff->stats.resizes = 0;
+    ff->stats.max_lines = ff->stats.max_words = ff->stats.max_source_bytes = 0;
+    ff->stats.total_read_bytes = ff->stats.max_read_size = 0;
 
     ff->lines = procfile_lines_create();
     ff->words = procfile_words_create();
 
+    ff->stats.memory = sizeof(procfile) + size +
+                       (sizeof(pflines) + ff->lines->size * sizeof(ffline)) +
+                       (sizeof(pfwords) + ff->words->size * sizeof(char *));
+
     procfile_set_separators(ff, separators);
 
     netdata_log_debug(D_PROCFILE, "File '%s' opened.", filename);
@@ -456,6 +496,7 @@ procfile *procfile_reopen(procfile *ff, const char *filename, const char *separa
         procfile_close(ff);
         return NULL;
     }
+    ff->stats.opens++;
 
     // netdata_log_info("PROCFILE: opened '%s' on fd %d", filename, ff->fd);
 
@@ -483,7 +524,7 @@ void procfile_print(procfile *ff) {
     for(l = 0; likely(l < lines) ;l++) {
         size_t words = procfile_linewords(ff, l);
 
-        netdata_log_debug(D_PROCFILE, " line %zu starts at word %zu and has %zu words", l, ff->lines->lines[l].first, ff->lines->lines[l].words);
+        netdata_log_debug(D_PROCFILE, " line %zu starts at word %zu and has %zu words", l, (size_t)ff->lines->lines[l].first, (size_t)ff->lines->lines[l].words);
 
         size_t w;
         for(w = 0; likely(w < words) ;w++) {

+ 21 - 6
src/libnetdata/procfile/procfile.h

@@ -19,9 +19,8 @@ typedef struct {
 // An array of lines
 
 typedef struct {
-    size_t words;   // how many words this line has
-    size_t first;   // the id of the first word of this line
-                    // in the words array
+    uint32_t words; // how many words this line has
+    uint32_t first; // the id of the first word of this line in the words array
 } ffline;
 
 typedef struct {
@@ -35,7 +34,7 @@ typedef struct {
 // The procfile
 
 #define PROCFILE_FLAG_DEFAULT             0x00000000 // To store inside `collector.log`
-#define PROCFILE_FLAG_NO_ERROR_ON_FILE_IO 0x00000001 // Do not store nothing
+#define PROCFILE_FLAG_NO_ERROR_ON_FILE_IO 0x00000001 // Do not log anything
 #define PROCFILE_FLAG_ERROR_ON_ERROR_LOG  0x00000002 // Store inside `error.log`
 
 typedef enum __attribute__ ((__packed__)) procfile_separator {
@@ -47,7 +46,22 @@ typedef enum __attribute__ ((__packed__)) procfile_separator {
     PF_CHAR_IS_CLOSE
 } PF_CHAR_TYPE;
 
+struct procfile_stats {
+    size_t opens;
+    size_t reads;
+    size_t resizes;
+    size_t memory;
+    size_t total_read_bytes;
+    size_t max_source_bytes;
+    size_t max_lines;
+    size_t max_words;
+    size_t max_read_size;
+};
+
+
 typedef struct procfile {
+    // this structure is malloc'd (you need to initialize it at procfile_open()
+
     char *filename;                 // not populated until procfile_filename() is called
     uint32_t flags;
     int fd;                         // the file descriptor
@@ -56,6 +70,7 @@ typedef struct procfile {
     pflines *lines;
     pfwords *words;
     PF_CHAR_TYPE separators[256];
+    struct procfile_stats stats;
     char data[];                    // allocated buffer to keep file contents
 } procfile;
 
@@ -85,8 +100,8 @@ char *procfile_filename(procfile *ff);
 // set to the O_XXXX flags, to have procfile_open and procfile_reopen use them when opening proc files
 extern int procfile_open_flags;
 
-// set this to 1, to have procfile adapt its initial buffer allocation to the max allocation used so far
-extern int procfile_adaptive_initial_allocation;
+// call this with true and the expected initial sizes to allow procfile learn the sizes needed
+void procfile_set_adaptive_allocation(bool enable, size_t bytes, size_t lines, size_t words);
 
 // return the number of lines present
 #define procfile_lines(ff) ((ff)->lines->len)

Some files were not shown because too many files changed in this diff