Repository navigation
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Advanced Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 8 included reviews per hour; 6 remain after this review. 📝 WalkthroughWalkthroughThe change adds optional Linux network monitoring through eBPF and Suggested reviewers: Priority: ➖ Normal Change: Feature Merge Risk: 🟡 Moderate · up to Network refreshes can stall on large hosts, and failed eBPF cleanup can leave load-time privileges active. These risks should be addressed before merging. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Packets trace through kernel streams Comment |
There was a problem hiding this comment.
Actionable comments posted: 11
Caution
Some comments are outside the diff and can’t be posted inline due to GitHub limitations.
⚠️ Outside diff range comments (1)
linux/LinuxProcessTable.c (1)
641-800: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winHandle realtime clock rollback in
NetRateWindow_update.
LinuxProcessTable_readIoFileandLinuxProcessTable_readNetIOare active scan paths at lines 1893-1897. They passhost->realtimeMstoNetRateWindow_update, whileScreenManager.cexplicitly detects realtime clock adjustments.saturatingSubprevents unsigned wraparound, but a backward step leaveswindow_start_msin the future. The window can retain stale rates, then calculate an inflated rate when the clock reaches the old window start because the denominator excludes the rollback interval. Usehost->monotonicMsfor these rate-window timestamps, or reseed whennow < w->window_start_ms.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Advanced
Run ID: addac728-0105-4ccc-809f-fa80a484b3a0
📒 Files selected for processing (20)
Makefile.amREADME.mdRow.cRow.hXUtils.cXUtils.hconfigure.achtop.1.inlinux/LinuxProcess.clinux/LinuxProcess.hlinux/LinuxProcessTable.clinux/NetLinkNet.clinux/NetLinkNet.hlinux/NetMonitor.bpf.clinux/NetMonitor.clinux/NetMonitor.hlinux/Platform.clinux/ProcessField.hpcp/PCPDynamicColumn.cpcp/PCPProcess.c
Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.
| static void NetLinkNet_rescanInodes(void) { | ||
| DIR* procDir = opendir(PROCDIR); | ||
| if (!procDir) | ||
| return; | ||
|
|
||
| Hashtable* freshInodePid = Hashtable_new(1024, false); | ||
| pidsPresent = Hashtable_new(256, false); | ||
|
|
||
| struct dirent* de; | ||
| while ((de = readdir(procDir)) != NULL) { | ||
| pid_t pid = (pid_t) strtol(de->d_name, NULL, 10); | ||
| if (pid <= 0) | ||
| continue; | ||
|
|
||
| unsigned int tgid = NetLinkNet_readTgid(pid); | ||
| if (!tgid) | ||
| continue; | ||
|
|
||
| if (!Hashtable_get(pidsPresent, tgid)) | ||
| Hashtable_put(pidsPresent, tgid, (void*) (uintptr_t) tgid); | ||
|
|
||
| char fdPath[64]; | ||
| xSnprintf(fdPath, sizeof(fdPath), PROCDIR "/%s/fd", de->d_name); | ||
| DIR* fdDir = opendir(fdPath); | ||
| if (!fdDir) | ||
| continue; | ||
|
|
||
| int fdDirFd = dirfd(fdDir); | ||
| struct dirent* fde; | ||
| while ((fde = readdir(fdDir)) != NULL) { | ||
| struct stat st; | ||
| if (fstatat(fdDirFd, fde->d_name, &st, 0) != 0) | ||
| continue; | ||
| if (S_ISSOCK(st.st_mode)) | ||
| Hashtable_put(freshInodePid, (ht_key_t) st.st_ino, (void*) (uintptr_t) tgid); | ||
| } | ||
| closedir(fdDir); | ||
| } | ||
| closedir(procDir); |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift
Bound the synchronous inode rescan.
ProcessTable_goThroughEntries() calls NetLinkNet_update() in the per-sample path. Every 5000 ms, NetLinkNet_update() synchronously calls NetLinkNet_rescanInodes(). The rescan calls fstatat() for every /proc/<pid>/fd entry, so its work scales with total open file descriptors and can delay refresh completion on high-FD hosts.
Preserve the complete inode-to-TGID map when reducing this work. The /proc/net/{tcp,tcp6,udp,udp6} dumps do not provide the owning TGID, and inodePid is required to attribute socket counters. readlinkat() still performs one operation per descriptor. An unchanged st_nlink also does not prove that descriptor ownership is unchanged. Consider moving the full rescan off the sample thread or implementing a correctness-preserving incremental scan. Raising NETLINK_INODE_RESCAN_MS only reduces frequency and increases mapping staleness.
There was a problem hiding this comment.
Maybe try if we can skip the fstatat if we know the fd is not a network socket or alike?
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ast-grep outline linux/NetLinkNet.c --items all --match 'NetLinkNet_rescanInodes|NetLinkNet_update|NetLinkNet_getNetBytes'
sed -n '219,380p' linux/NetLinkNet.c
rg -n -C 2 'fstatat\(|readlinkat\(|d_type|DT_' linux README.mdLength of output: 9261
@BenBE, not before inspecting the descriptor target.
Each /proc/<pid>/fd/<n> entry is a symlink. readdir() metadata identifies the entry as a symlink, not whether its target is a socket. fstatat(..., 0) follows that link and supplies the target type and inode.
readlinkat() can detect socket:[<inode>], but it still performs one operation for each descriptor. It replaces fstatat(); it does not bound the scan.
A socket-inode set from the NETLINK_SOCK_DIAG dump can avoid unnecessary hash-table work after inspection. It cannot safely skip inspection because descriptor ownership can change between rescans. The original concern therefore remains: a correctness-preserving optimization needs to move or incrementally schedule the full descriptor walk, rather than prefilter entries by type.
You are interacting with an AI system.
| void* links[NETMONITOR_MAX_PROGRAMS]; | ||
| size_t nLinks = 0; | ||
| #if !defined(NDEBUG) | ||
| struct NetMonitorAttachResult { | ||
| const char* section; | ||
| bool attached; | ||
| int error; | ||
| } results[NETMONITOR_MAX_PROGRAMS]; | ||
| size_t nResults = 0; | ||
| bool anyNotAllowed = false; | ||
| #endif | ||
| void* prog = NULL; | ||
| while ((prog = sym_bpf_object__next_program(obj, prog)) != NULL) { | ||
| void* link = sym_bpf_program__attach(prog); | ||
| #if !defined(NDEBUG) | ||
| int error = link ? 0 : errno; | ||
| if (nResults < NETMONITOR_MAX_PROGRAMS) { | ||
| results[nResults].section = sym_bpf_program__section_name(prog); | ||
| results[nResults].attached = link != NULL; | ||
| results[nResults].error = error; | ||
| nResults++; | ||
| } | ||
| #endif | ||
| if (!link) { | ||
| #if !defined(NDEBUG) | ||
| if (error == EPERM) | ||
| anyNotAllowed = true; | ||
| #endif | ||
| } else if (nLinks < NETMONITOR_MAX_PROGRAMS) { | ||
| links[nLinks++] = link; | ||
| } | ||
| } | ||
| if (!nLinks) { | ||
| for (size_t i = 0; i < nLinks; i++) | ||
| sym_bpf_link__destroy(links[i]); | ||
| goto fail; | ||
| } | ||
| #if !defined(NDEBUG) | ||
| NetMonitor_debug("eBPF probe attach summary (load succeeded):\n"); | ||
| for (size_t i = 0; i < nResults; i++) { | ||
| const char* section = results[i].section ? results[i].section : "<unknown>"; | ||
| if (results[i].attached) { | ||
| NetMonitor_debug(" %-26s attached\n", section); | ||
| } else if (results[i].error == ENOENT) { | ||
| const char* note = NetMonitor_expectedMissing(section); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Require complete core probe coverage before enabling exact mode.
NetMonitor_loadBPF() sets eBPFActive when any probe attaches. It does not require the TCP and UDP send/receive probes or ping_recvmsg. If one core probe fails and another attaches, LinuxProcessTable_readNetIO() still selects the eBPF counters, while LinuxProcess_netMarker() returns no marker. The affected direction or protocol is then omitted from the displayed rate without indicating degraded support.
Require the TCP and UDP direction probes and ping_recvmsg before setting eBPFActive. Keep only the documented expected-missing exceptions for ping_v4_sendmsg and ping_v6_sendmsg. If a required probe is missing, destroy attached links and use the fallback backend.
0a4d0f6 to
7a1cd14
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Advanced
Run ID: 9906f8ca-4c3e-4866-bc2b-3e35932fd4ba
📒 Files selected for processing (8)
Makefile.amREADME.mdconfigure.achtop.1.inlinux/LinuxProcessTable.clinux/NetMonitor.clinux/Platform.clinux/Platform.h
Included review availability: Your plan provides up to 8 included reviews per hour; 6 remain after this review.
00a5b79 to
b53703e
Compare
| [NET_RX] = { .name = "NET_RX", .title = " NET RX ", .description = "Network receive rate in bytes per second (eBPF exact; + netlink socket counters; ~ read(2)/write(2) estimate)", .flags = PROCESS_FLAG_IO | PROCESS_FLAG_LINUX_NETIO, .defaultSortDesc = true, }, | ||
| [NET_TX] = { .name = "NET_TX", .title = " NET TX ", .description = "Network transmit rate in bytes per second (eBPF exact; + netlink socket counters; ~ read(2)/write(2) estimate)", .flags = PROCESS_FLAG_IO | PROCESS_FLAG_LINUX_NETIO, .defaultSortDesc = true, }, | ||
| [NET_TOTAL] = { .name = "NET_TOTAL", .title = " NET TOTAL ", .description = "Total network rate in bytes per second (eBPF exact; + netlink socket counters; ~ read(2)/write(2) estimate)", .flags = PROCESS_FLAG_IO | PROCESS_FLAG_LINUX_NETIO, .defaultSortDesc = true, }, |
There was a problem hiding this comment.
Maybe have them with define guards like delay accounting below? So only when wither netlink or ebpf collection is enabled in configure?
| /* A cumulative counter that went backwards means the tracking state was | ||
| * reset elsewhere (dropped map entry, PID reuse, readahead counted in | ||
| * read_bytes before the corresponding rchar). The previous baseline no | ||
| * longer applies, so re-seed instead of trusting a bogus delta that would | ||
| * surface as a huge rate. */ | ||
| if (curRx < w->last_rx_bytes || curTx < w->last_tx_bytes) { |
There was a problem hiding this comment.
Does this properly account for wrap-around?
There was a problem hiding this comment.
Given how little we need from libnl for the actual socket setup, a straight up socket-only implementation (without libnl) would be even shorter (one-shot with Copilot):
#include <dirent.h>
#include <errno.h>
#include <limits.h>
#include <linux/inet_diag.h>
#include <linux/netlink.h>
#include <linux/sock_diag.h>
#include <linux/tcp.h>
#include <netinet/in.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "Hashtable.h"
#include "Macros.h"
#include "XUtils.h"
#include "linux/LinuxMachine.h"
#include "linux/Platform.h"
#define NETLINK_INODE_RESCAN_MS 5000
#define NETLINK_RECV_BUFSIZE (256 * 1024)
#ifndef TCP_LISTEN
#define TCP_LISTEN 10
#endif
#define NLA_ALIGNTO 4
#define NLA_ALIGN(len) (((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1))
#define NLA_HDRLEN ((int)NLA_ALIGN(sizeof(struct nlattr)))
#define NLA_DATA(nla) \
((void*)((char*)(nla) + NLA_HDRLEN))
#define NLA_LEN(nla) \
((int)((nla)->nla_len - NLA_HDRLEN))
#define NLA_OK(nla, len) \
((len) >= (int)sizeof(struct nlattr) && \
(nla)->nla_len >= sizeof(struct nlattr) && \
(nla)->nla_len <= (unsigned)(len))
#define NLA_NEXT(nla, len) \
((len) -= NLA_ALIGN((nla)->nla_len), \
(struct nlattr*)(void*)((char*)(nla) + \
NLA_ALIGN((nla)->nla_len)))
typedef struct SocketState_ {
unsigned long long lastRx;
unsigned long long lastTx;
unsigned int round;
} SocketState;
typedef struct PidTotals_ {
unsigned long long rx;
unsigned long long tx;
} PidTotals;
static int diagFd = -1;
static uint32_t nlSeq = 0;
static Hashtable* inodePid = NULL;
static Hashtable* socketState = NULL;
static Hashtable* pidTotals = NULL;
static Hashtable* pidsPresent = NULL;
static Hashtable* unreadablePids = NULL;
static unsigned int roundCounter = 0;
static uint64_t lastRescanMs = 0;
static int gDumpProto = 0;
static void NetLinkNet_debug(const char* fmt, ...) ATTR_FORMAT(printf, 1, 2);
static void NetLinkNet_debug(const char* fmt, ...) {
#ifdef NDEBUG
(void)fmt;
#else
va_list ap;
va_start(ap, fmt);
fprintf(stderr, "htop-netlink: ");
vfprintf(stderr, fmt, ap);
va_end(ap);
#endif
}
static inline unsigned long long
saturatingAdd(unsigned long long a,
unsigned long long b) {
return a > ULLONG_MAX - b ? ULLONG_MAX : a + b;
}
/*
* Keep the inode scanning code from the existing file
* unchanged.
*
* NetLinkNet_readTgid()
* NetLinkNet_rescanInodes()
* NetLinkNet_collectStaleSocket()
* NetLinkNet_dropStaleSocket()
* NetLinkNet_pruneStaleSockets()
* NetLinkNet_collectStalePid()
* NetLinkNet_dropStalePid()
*/
/*****************************************************************************/
static void NetLinkNet_closeSocket(void) {
if (diagFd >= 0)
close(diagFd);
diagFd = -1;
}
static bool NetLinkNet_open(void) {
diagFd =
socket(AF_NETLINK,
SOCK_RAW | SOCK_CLOEXEC,
NETLINK_SOCK_DIAG);
if (diagFd < 0)
return false;
int rcvbuf = NETLINK_RECV_BUFSIZE;
(void)setsockopt(diagFd,
SOL_SOCKET,
SO_RCVBUF,
&rcvbuf,
sizeof(rcvbuf));
struct sockaddr_nl local;
memset(&local, 0, sizeof(local));
local.nl_family = AF_NETLINK;
if (bind(diagFd,
(struct sockaddr*)&local,
sizeof(local)) < 0) {
NetLinkNet_closeSocket();
return false;
}
NetLinkNet_debug(
"netlink network monitoring active\n");
return true;
}
/*****************************************************************************/
static void NetLinkNet_accumulate(unsigned int inode,
unsigned long long rx,
unsigned long long tx) {
SocketState* state =
Hashtable_get(socketState, inode);
if (!state) {
state = xCalloc(1, sizeof(*state));
state->lastRx = rx;
state->lastTx = tx;
state->round = roundCounter;
Hashtable_put(socketState, inode, state);
return;
}
state->round = roundCounter;
unsigned long long rxDelta =
saturatingSub(rx, state->lastRx);
unsigned long long txDelta =
saturatingSub(tx, state->lastTx);
state->lastRx = rx;
state->lastTx = tx;
if (!rxDelta && !txDelta)
return;
unsigned int pid =
(unsigned int)(uintptr_t)
Hashtable_get(inodePid, inode);
if (!pid)
return;
PidTotals* totals =
Hashtable_get(pidTotals, pid);
if (!totals) {
totals = xCalloc(1, sizeof(*totals));
Hashtable_put(pidTotals, pid, totals);
}
totals->rx =
saturatingAdd(totals->rx, rxDelta);
totals->tx =
saturatingAdd(totals->tx, txDelta);
}
/*****************************************************************************/
static void NetLinkNet_processMsg(struct nlmsghdr* nlh) {
if (nlh->nlmsg_len <
NLMSG_LENGTH(sizeof(struct inet_diag_msg)))
return;
struct inet_diag_msg* dm = NLMSG_DATA(nlh);
if (!dm->idiag_inode)
return;
if (gDumpProto == IPPROTO_TCP &&
dm->idiag_state == TCP_LISTEN)
return;
unsigned long long rx = 0;
unsigned long long tx = 0;
int remaining =
(int)(nlh->nlmsg_len -
NLMSG_LENGTH(sizeof(*dm)));
struct nlattr* attr =
(struct nlattr*)(void*)(dm + 1);
for (; NLA_OK(attr, remaining);
attr = NLA_NEXT(attr, remaining)) {
int type =
attr->nla_type & 0x3fff;
void* data = NLA_DATA(attr);
int len = NLA_LEN(attr);
if (gDumpProto == IPPROTO_TCP &&
type == INET_DIAG_INFO) {
int offsetRx =
(int)offsetof(
struct tcp_info,
tcpi_bytes_received);
int offsetTx =
(int)offsetof(
struct tcp_info,
tcpi_bytes_acked);
if (len >= offsetRx + 8)
memcpy(&rx,
(char*)data + offsetRx,
sizeof(rx));
if (len >= offsetTx + 8)
memcpy(&tx,
(char*)data + offsetTx,
sizeof(tx));
} else if (gDumpProto != IPPROTO_TCP &&
type == INET_DIAG_MEMINFO) {
uint32_t* mem = data;
if (len >= (int)(5 * sizeof(uint32_t))) {
rx = mem[0];
tx = mem[3];
} else if (len >= (int)(2 * sizeof(uint32_t))) {
rx = mem[0];
tx = mem[1];
}
}
}
NetLinkNet_accumulate(dm->idiag_inode,
rx,
tx);
}
/*****************************************************************************/
static int NetLinkNet_dumpOnce(int family,
int proto) {
struct {
struct nlmsghdr nlh;
struct inet_diag_req_v2 req;
} request;
memset(&request, 0, sizeof(request));
request.nlh.nlmsg_len =
NLMSG_LENGTH(sizeof(request.req));
request.nlh.nlmsg_type =
SOCK_DIAG_BY_FAMILY;
request.nlh.nlmsg_flags =
NLM_F_REQUEST |
NLM_F_DUMP;
request.nlh.nlmsg_seq =
++nlSeq;
request.req.sdiag_family =
(unsigned char)family;
request.req.sdiag_protocol =
(unsigned char)proto;
request.req.idiag_ext =
(1 << (INET_DIAG_MEMINFO - 1)) |
(1 << (INET_DIAG_INFO - 1));
request.req.idiag_states = ~0U;
gDumpProto = proto;
if (send(diagFd,
&request,
sizeof(request),
0) < 0)
return -1;
for (;;) {
char buffer[NETLINK_RECV_BUFSIZE];
struct sockaddr_nl nladdr;
struct iovec iov = {
.iov_base = buffer,
.iov_len = sizeof(buffer),
};
struct msghdr msg;
memset(&msg, 0, sizeof(msg));
msg.msg_name = &nladdr;
msg.msg_namelen = sizeof(nladdr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
ssize_t len =
recvmsg(diagFd, &msg, 0);
if (len < 0)
return -1;
if (nladdr.nl_pid != 0)
continue;
struct nlmsghdr* nlh;
for (nlh = (struct nlmsghdr*)buffer;
NLMSG_OK(nlh, len);
nlh = NLMSG_NEXT(nlh, len)) {
if (nlh->nlmsg_seq != nlSeq)
continue;
switch (nlh->nlmsg_type) {
case NLMSG_DONE:
return 0;
case NLMSG_ERROR: {
struct nlmsgerr* err =
NLMSG_DATA(nlh);
if (err->error)
return -1;
break;
}
default:
NetLinkNet_processMsg(nlh);
break;
}
}
}
}
/*****************************************************************************/
void NetLinkNet_init(void) {
diagFd = -1;
nlSeq = 0;
inodePid = NULL;
socketState = NULL;
pidTotals = NULL;
pidsPresent = NULL;
unreadablePids = NULL;
roundCounter = 0;
lastRescanMs = 0;
}
void NetLinkNet_done(void) {
NetLinkNet_closeSocket();
if (inodePid)
Hashtable_delete(inodePid);
if (socketState)
Hashtable_delete(socketState);
if (pidTotals)
Hashtable_delete(pidTotals);
if (pidsPresent)
Hashtable_delete(pidsPresent);
if (unreadablePids)
Hashtable_delete(unreadablePids);
inodePid = NULL;
socketState = NULL;
pidTotals = NULL;
pidsPresent = NULL;
unreadablePids = NULL;
}
bool NetLinkNet_isActive(void) {
return diagFd >= 0;
}
void NetLinkNet_update(void) {
if (diagFd < 0 && !NetLinkNet_open())
return;
if (!socketState)
socketState = Hashtable_new(512, true);
if (!pidTotals)
pidTotals = Hashtable_new(128, true);
uint64_t nowMs;
Platform_gettime_monotonic(&nowMs);
if (!inodePid ||
nowMs - lastRescanMs >=
NETLINK_INODE_RESCAN_MS) {
NetLinkNet_rescanInodes();
lastRescanMs = nowMs;
}
roundCounter++;
static const struct {
int family;
int proto;
} combos[] = {
{ AF_INET, IPPROTO_TCP },
{ AF_INET, IPPROTO_UDP },
{ AF_INET6, IPPROTO_TCP },
{ AF_INET6, IPPROTO_UDP },
};
bool complete = true;
for (size_t i = 0;
i < ARRAYSIZE(combos);
i++) {
if (NetLinkNet_dumpOnce(
combos[i].family,
combos[i].proto) < 0) {
complete = false;
break;
}
}
NetLinkNet_parseProcNetIcmp(PROCDIR "/net/icmp");
NetLinkNet_parseProcNetIcmp(PROCDIR "/net/icmp6");
if (complete)
NetLinkNet_pruneStaleSockets();
else
NetLinkNet_closeSocket();
}
bool NetLinkNet_getNetBytes(pid_t pid,
unsigned long long* rx,
unsigned long long* tx) {
if (!pidTotals)
return false;
PidTotals* totals =
Hashtable_get(pidTotals,
(ht_key_t)pid);
if (!totals)
return false;
*rx = totals->rx;
*tx = totals->tx;
return true;
}
bool NetLinkNet_isProcessUnreadable(pid_t pid) {
if (!unreadablePids)
return false;
return Hashtable_get(
unreadablePids,
(ht_key_t)pid) != NULL;
}
With some further cleanup that's like 20-25% less code and SOCK_DIAG not depending on libnl …
|
|
||
| eBPF program for per-process network bandwidth accounting. | ||
| Compiled at build time with clang into an ELF object which is | ||
| embedded into the htop binary and loaded via libbpf. |
There was a problem hiding this comment.
Not quite the canonical format for this initial header comment … ;-)
| } | ||
| } | ||
|
|
||
| NetMonitor_debug("CapEff=0x%llx (need CAP_BPF or CAP_SYS_ADMIN), lockdown=%s, perf_event_paranoid=%s\n", |
There was a problem hiding this comment.
Providing the mask for CAP_BPF and CAP_SYS_ADMIN might improve UX when debugging this error message … ;-)
| NetLinkNet_done(); | ||
| NetMonitor_done(); |
There was a problem hiding this comment.
Should release in reverse init order.
| NetLinkNet_done(); | |
| NetMonitor_done(); | |
| NetMonitor_done(); | |
| NetLinkNet_done(); |
| fi | ||
|
|
||
|
|
||
| AC_ARG_ENABLE( |
There was a problem hiding this comment.
Not directly of consequence for this PR, but possibly split --enable-ebpf (General eBPF setup) and --enable-ebpf-net (ebpf code used for network traffic accounting). Later PRs could then introduce e.g. --enable-ebpf-proc or similar.
--enable-ebpf should be marked auto or implied when any of its sub-features are present (subfeatures failing if eBPF detection fails).
| .B NET_TOTAL (NET TOTAL) | ||
| The total network rate, NET_RX + NET_TX (see above). | ||
| .TP |
There was a problem hiding this comment.
Why not use NET_TOTAL for the wall of text and keep NET_RX and NET_TX as a small not for "just the RX/TX part"? Makes the description more symmetrical.
| /* clang ignores the access attribute and emits -Wunknown-attributes for it, | ||
| * so only define it when the compiler actually supports it. */ | ||
| #if defined(HAVE_ATTR_ACCESS) && !defined(__clang__) |
There was a problem hiding this comment.
Seems like the check for this in configure.ac is off somehow?
| - dependencies: *libnl-3-dev*(build-time) and *libnl-genl-3-dev*(build-time), at runtime *libnl-3* and *libnl-genl-3* are loaded via `dlopen(3)` if available and requested | ||
| - default: *check* | ||
|
|
||
| * `--enable-ebpf`: |
There was a problem hiding this comment.
I'd suggest
| * `--enable-ebpf`: | |
| * `--enable-ebpf-net`: |
to accommodate for future extensions.
These come in three flavours:
Exact: eBPF Kprobes; these need libbpf, root or CAP_BPF/CAP_SYS_ADMIN
at runtime. And clang with assorted horsetoppings at build time.
This measures TCP, UDP and ICMP both IPv4 and IPv6.
--enable-ebpf-net, no prefix in the columns.
Concise: NETLINK_SOCK_DIAG based; this needs libnl-3 (might the there from
delayacct already). This measures TCP and gets a lower bound from
socket-buffer queue levels for UDP and ICMP).
--enable-libnl-net, "+" in the columns.
Estimate: This uses read/write byte counters and substracts disk I/O, so
it is crap when pipes are being used, too.
Always enabled, "~" in the columns.
Assisted-by: OpenCode Zen
…se I/O rate accounting Assisted-by: OpenCode Zen
…ing average estimator ... makes for nicer, more continues data points ~ smoother UI
… aspirations more easy to integrate
3e4c1ab to
53755b4
Compare
These come in three flavors:
Exact: eBPF Kprobes; these need libbpf, root or CAP_BPF/CAP_SYS_ADMIN
at runtime. And clang with assorted horsetoppings at build time.
This measures TCP, UDP and ICMP both IPv4 and IPv6.
--enable-ebpf-net, no prefix in the columns.
Concise: NETLINK_SOCK_DIAG based; this needs libnl-3 (might the there from
delayacct already). This measures TCP and gets a lower bound from
socket-buffer queue levels for UDP and ICMP).
--enable-libnl-net, "+" in the columns.
Estimate: This uses read/write byte counters and subtracts disk I/O, so
it is crap when pipes are being used, too.
Always enabled, "~" in the columns.
This is a massive feature, so it needs thorough testing and review.
There is helpful diagnostic printing on stderr when
--enable-debugis configured. We may want to move that to a separate IFDEF or drop before merging this feature. At least no other features babble on stderr to make our lives easier.This is a Linux-only feature.
Developed and tested on Debian, so testing on other distros and kernels is most appreciated.