From 5e2c35700b7723ded9f03d971ea9588b0c385b77 Mon Sep 17 00:00:00 2001 From: anomen Date: Mon, 13 Jun 2016 22:22:51 +0200 Subject: [PATCH 1/2] Add -l option to show full command line (fixes #23) --- doc/nethogs.8 | 9 ++++++- src/cui.cpp | 62 ++++++++++++++++++++++++++++++++++++++++------ src/inode2prog.cpp | 26 ++++++++++++++++--- src/inode2prog.h | 2 +- src/main.cpp | 9 +++++-- src/nethogs.cpp | 1 + src/process.cpp | 6 ++++- src/process.h | 9 ++++++- 8 files changed, 107 insertions(+), 17 deletions(-) diff --git a/doc/nethogs.8 b/doc/nethogs.8 index ea2bcce..7a3e9a2 100644 --- a/doc/nethogs.8 +++ b/doc/nethogs.8 @@ -15,6 +15,7 @@ nethogs \- Net top tool grouping bandwidth per process .RB [ "\-p" ] .RB [ "\-a" ] .RB [ "\-s" ] +.RB [ "\-l" ] .RI [device(s)] .SH DESCRIPTION NetHogs is a small 'net top' tool. Instead of breaking the traffic down per protocol or per subnet, like most such tools do, it groups bandwidth by process - and does not rely on a special kernel module to be loaded. So if there's suddenly a lot of network traffic, you can fire up NetHogs and immediately see which PID is causing this, and if it's some kind of spinning process, kill it. @@ -47,6 +48,9 @@ limit number of refreshes .TP \fB-s\fP sort by traffic sent +.TP +\fB-l\fP +display command line .PP .I device(s) to monitor. By default eth0 is being used @@ -56,8 +60,11 @@ to monitor. By default eth0 is being used m cycle between display modes (KB/s, KB, B, MB) .TP +l +display command line +.TP r -sort by 'received' +sort by 'received' .TP s sort by 'sent' diff --git a/src/cui.cpp b/src/cui.cpp index d5197a9..7683f5c 100644 --- a/src/cui.cpp +++ b/src/cui.cpp @@ -45,6 +45,7 @@ extern Process *unknownip; extern bool sortRecv; extern int viewMode; +extern bool showcommandline; extern unsigned refreshlimit; extern unsigned refreshcount; @@ -64,11 +65,12 @@ const char *COLUMN_FORMAT_RECEIVED = "%11.3f"; class Line { public: - Line(const char *name, double n_recv_value, double n_sent_value, pid_t pid, - uid_t uid, const char *n_devicename) { + Line(const char *name, const char *cmdline, double n_recv_value, + double n_sent_value, pid_t pid, uid_t uid, const char *n_devicename) { assert(pid >= 0); assert(pid <= PID_MAX); m_name = name; + m_cmdline = cmdline; sent_value = n_sent_value; recv_value = n_recv_value; devicename = n_devicename; @@ -85,6 +87,7 @@ public: private: const char *m_name; + const char *m_cmdline; const char *devicename; pid_t m_pid; uid_t m_uid; @@ -152,6 +155,43 @@ static void mvaddstr_truncate_trailing(int row, int col, const char *str, } } +/** + * Render the provided progname and cmdline at the specified location, + * truncating if the length of the values exceeds a maximum. + * If the text must be truncated, the text will be rendered up to max_len - 2 + * characters and then ".." will be rendered. + * cmdline is truncated first and then progname. + */ +static void mvaddstr_truncate_cmdline(int row, int col, const char *progname, + const char *cmdline, + std::size_t max_len) { + std::size_t proglen = strlen(progname); + std::size_t max_cmdlen; + + if (proglen > max_len) { + mvaddnstr(row, col, progname, max_len - 2); + addstr(".."); + max_cmdlen = 0; + } else { + mvaddstr(row, col, progname); + max_cmdlen = max_len - proglen - 1; + } + + if (showcommandline && cmdline) { + + std::size_t cmdlinelen = strlen(cmdline); + + if ((cmdlinelen + 1) > max_cmdlen) { + if (max_cmdlen >= 3) { + mvaddnstr(row, col + proglen + 1, cmdline, max_cmdlen - 3); + addstr(".."); + } + } else { + mvaddstr(row, col + proglen + 1, cmdline); + } + } +} + void Line::show(int row, unsigned int proglen) { assert(m_pid >= 0); assert(m_pid <= PID_MAX); @@ -175,7 +215,7 @@ void Line::show(int row, unsigned int proglen) { mvaddstr_truncate_trailing(row, column_offset_user, username.c_str(), username.size(), COLUMN_WIDTH_USER); - mvaddstr_truncate_leading(row, column_offset_program, m_name, strlen(m_name), + mvaddstr_truncate_cmdline(row, column_offset_program, m_name, m_cmdline, proglen); mvaddstr(row, column_offset_dev, devicename); @@ -195,8 +235,10 @@ void Line::show(int row, unsigned int proglen) { } void Line::log() { - std::cout << m_name << '/' << m_pid << '/' << m_uid << "\t" << sent_value - << "\t" << recv_value << std::endl; + std::cout << m_name; + if (showcommandline && m_cmdline) + std::cout << ' ' << m_cmdline; + std::cout << '/' << m_pid << '/' << m_uid << "\t" << sent_value << "\t" << recv_value << std::endl; } int GreatestFirst(const void *ma, const void *mb) { @@ -258,6 +300,10 @@ void ui_tick() { /* sort on 'received' */ sortRecv = true; break; + case 'l': + /* show cmdline' */ + showcommandline = !showcommandline; + break; case 'm': /* switch mode: total vs kb/s */ viewMode = (viewMode + 1) % VIEWMODE_COUNT; @@ -386,9 +432,9 @@ void do_refresh() { assert(curproc->getVal()->pid >= 0); assert(n < nproc); - lines[n] = - new Line(curproc->getVal()->name, value_recv, value_sent, - curproc->getVal()->pid, uid, curproc->getVal()->devicename); + lines[n] = new Line(curproc->getVal()->name, curproc->getVal()->cmdline, + value_recv, value_sent, curproc->getVal()->pid, uid, + curproc->getVal()->devicename); curproc = curproc->next; n++; } diff --git a/src/inode2prog.cpp b/src/inode2prog.cpp index e2b7972..3c6ee12 100644 --- a/src/inode2prog.cpp +++ b/src/inode2prog.cpp @@ -116,12 +116,32 @@ static std::string read_file(const char *filepath) { return contents; } -std::string getprogname(pid_t pid) { +std::string getcmdline(pid_t pid) { const int maxfilenamelen = 14 + MAX_PID_LENGTH + 1; char filename[maxfilenamelen]; std::snprintf(filename, maxfilenamelen, "/proc/%d/cmdline", pid); - return read_file(filename); + + bool replace_null = false; + std::string cmdline = read_file(filename); + + // join parameters, keep prgname separate, don't overwrite trailing null + for (int idx = 0; idx < (cmdline.length() - 1); idx++) { + if (cmdline[idx] == 0x00) { + if (replace_null) { + cmdline[idx] = ' '; + } + replace_null = true; + } + } + + if (cmdline.length() == 0 || (cmdline[cmdline.length() - 1] != 0x00)) { + // invalid content of cmdline file. Add null char to allow further + // processing. + cmdline.append("\0"); + } + + return cmdline; } void setnode(unsigned long inode, pid_t pid) { @@ -131,7 +151,7 @@ void setnode(unsigned long inode, pid_t pid) { prg_node *newnode = new prg_node; newnode->inode = inode; newnode->pid = pid; - newnode->name = getprogname(pid); + newnode->cmdline = getcmdline(pid); inodeproc[inode] = newnode; delete current_value; diff --git a/src/inode2prog.h b/src/inode2prog.h index 2a3ad8f..c9b0244 100644 --- a/src/inode2prog.h +++ b/src/inode2prog.h @@ -31,7 +31,7 @@ struct prg_node { long inode; pid_t pid; - std::string name; + std::string cmdline; }; struct prg_node *findPID(unsigned long inode); diff --git a/src/main.cpp b/src/main.cpp index 1bfe109..c0174e2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -27,7 +27,7 @@ static void help(bool iserror) { // output << "usage: nethogs [-V] [-b] [-d seconds] [-t] [-p] [-f (eth|ppp))] // [device [device [device ...]]]\n"; output << "usage: nethogs [-V] [-h] [-b] [-d seconds] [-v mode] [-c count] " - "[-t] [-p] [-s] [-a] [device [device [device ...]]]\n"; + "[-t] [-p] [-s] [-a] [-l] [device [device [device ...]]]\n"; output << " -V : prints version.\n"; output << " -h : prints this help.\n"; output << " -b : bughunt mode - implies tracemode.\n"; @@ -41,6 +41,7 @@ static void help(bool iserror) { // eth.\n"; output << " -p : sniff in promiscious mode (not recommended).\n"; output << " -s : sort output by sent column.\n"; + output << " -l : display command line.\n"; output << " -a : monitor all devices, even loopback/stopped ones.\n"; output << " device : device(s) to monitor. default is all " "interfaces up and running excluding loopback\n"; @@ -49,6 +50,7 @@ static void help(bool iserror) { output << " q: quit\n"; output << " s: sort by SENT traffic\n"; output << " r: sort by RECEIVE traffic\n"; + output << " l: display command line\n"; output << " m: switch between total (KB, B, MB) and KB/s mode\n"; } @@ -133,7 +135,7 @@ int main(int argc, char **argv) { bool all = false; int opt; - while ((opt = getopt(argc, argv, "Vhbtpsd:v:c:a")) != -1) { + while ((opt = getopt(argc, argv, "Vhbtpsd:v:c:la")) != -1) { switch (opt) { case 'V': versiondisplay(); @@ -163,6 +165,9 @@ int main(int argc, char **argv) { case 'c': refreshlimit = atoi(optarg); break; + case 'l': + showcommandline = true; + break; case 'a': all = true; break; diff --git a/src/nethogs.cpp b/src/nethogs.cpp index 180a1a3..7abea29 100644 --- a/src/nethogs.cpp +++ b/src/nethogs.cpp @@ -59,6 +59,7 @@ bool tracemode = false; bool bughuntmode = false; // sort on sent or received? bool sortRecv = true; +bool showcommandline = false; // viewMode: kb/s or total int viewMode = VIEWMODE_KBPS; const char version[] = " version " VERSION; diff --git a/src/process.cpp b/src/process.cpp index b42c1d2..86f8643 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -225,7 +225,11 @@ Process *getProcess(unsigned long inode, const char *devicename) { if (proc != NULL) return proc; - Process *newproc = new Process(inode, devicename, node->name.c_str()); + // extract program name and command line from data read from cmdline file + const char *prgname = node->cmdline.c_str(); + const char *cmdline = prgname + strlen(prgname) + 1; + + Process *newproc = new Process(inode, devicename, prgname, cmdline); newproc->pid = node->pid; char procdir[100]; diff --git a/src/process.h b/src/process.h index ecc30bf..598e439 100644 --- a/src/process.h +++ b/src/process.h @@ -57,7 +57,7 @@ public: /* the process makes a copy of the name. the device name needs to be stable. */ Process(const unsigned long m_inode, const char *m_devicename, - const char *m_name = NULL) + const char *m_name = NULL, const char *m_cmdline = NULL) : inode(m_inode) { // std::cout << "ARN: Process created with dev " << m_devicename << // std::endl; @@ -69,6 +69,11 @@ public: else name = strdup(m_name); + if (m_cmdline == NULL) + cmdline = NULL; + else + cmdline = strdup(m_cmdline); + devicename = m_devicename; connections = NULL; pid = 0; @@ -78,6 +83,7 @@ public: ~Process() { free(name); + free(cmdline); if (DEBUG) std::cout << "PROC: Process deleted at " << this << std::endl; } @@ -90,6 +96,7 @@ public: void gettotalb(float *recvd, float *sent); char *name; + char *cmdline; const char *devicename; int pid; From 2d829675a86483b30bc6896dce1c9866dd5ccecc Mon Sep 17 00:00:00 2001 From: anomen Date: Sun, 1 Jan 2017 20:49:38 +0100 Subject: [PATCH 2/2] cleanup --- src/cui.cpp | 17 ----------------- src/inode2prog.cpp | 2 +- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/src/cui.cpp b/src/cui.cpp index 7683f5c..d365a16 100644 --- a/src/cui.cpp +++ b/src/cui.cpp @@ -121,23 +121,6 @@ std::string uid2username(uid_t uid) { return std::string(pwd->pw_name); } -/** - * Render the provided text at the specified location, truncating if the length - * of the text exceeds a maximum. If the - * text must be truncated, the string ".." will be rendered, followed by max_len - * - 2 characters of the provided text. - */ -static void mvaddstr_truncate_leading(int row, int col, const char *str, - std::size_t str_len, - std::size_t max_len) { - if (str_len < max_len) { - mvaddstr(row, col, str); - } else { - mvaddstr(row, col, ".."); - addnstr(str + 2, max_len - 2); - } -} - /** * Render the provided text at the specified location, truncating if the length * of the text exceeds a maximum. If the diff --git a/src/inode2prog.cpp b/src/inode2prog.cpp index 3c6ee12..3cf7bf4 100644 --- a/src/inode2prog.cpp +++ b/src/inode2prog.cpp @@ -126,7 +126,7 @@ std::string getcmdline(pid_t pid) { std::string cmdline = read_file(filename); // join parameters, keep prgname separate, don't overwrite trailing null - for (int idx = 0; idx < (cmdline.length() - 1); idx++) { + for (size_t idx = 0; idx < (cmdline.length() - 1); idx++) { if (cmdline[idx] == 0x00) { if (replace_null) { cmdline[idx] = ' ';