464 lines
12 KiB
C++
464 lines
12 KiB
C++
/*
|
|
* cui.cpp
|
|
*
|
|
* Copyright (c) 2004-2006,2008,2010,2011 Arnout Engelen
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
|
*USA.
|
|
*
|
|
*/
|
|
|
|
/* NetHogs console UI */
|
|
#include <algorithm>
|
|
#include <cerrno>
|
|
#include <cstdlib>
|
|
#include <pwd.h>
|
|
#include <string>
|
|
#include <strings.h>
|
|
#include <sys/types.h>
|
|
|
|
#include "nethogs.h"
|
|
#include "process.h"
|
|
#include <ncurses.h>
|
|
|
|
std::string *caption;
|
|
static int cursOrig;
|
|
extern const char version[];
|
|
extern ProcList *processes;
|
|
extern timeval curtime;
|
|
|
|
extern Process *unknowntcp;
|
|
extern Process *unknownudp;
|
|
extern Process *unknownip;
|
|
|
|
extern bool sortRecv;
|
|
|
|
extern int viewMode;
|
|
extern bool showcommandline;
|
|
extern bool showBasename;
|
|
|
|
extern unsigned refreshlimit;
|
|
extern unsigned refreshcount;
|
|
|
|
#define PID_MAX 4194303
|
|
|
|
const int COLUMN_WIDTH_PID = 7;
|
|
const int COLUMN_WIDTH_USER = 8;
|
|
const int MAX_COLUMN_WIDTH_DEV = 15;
|
|
const int MIN_COLUMN_WIDTH_DEV = 5;
|
|
const int COLUMN_WIDTH_SENT = 11;
|
|
const int COLUMN_WIDTH_RECEIVED = 11;
|
|
const int COLUMN_WIDTH_UNIT = 6;
|
|
|
|
const char *COLUMN_FORMAT_PID = "%7d";
|
|
const char *COLUMN_FORMAT_SENT = "%11.3f";
|
|
const char *COLUMN_FORMAT_RECEIVED = "%11.3f";
|
|
|
|
// All descriptions are padded to 6 characters in length with spaces
|
|
const char *const desc_view_mode[VIEWMODE_COUNT] = {
|
|
"KB/s ", "KB ", "B ", "MB ", "MB/s ", "GB/s "};
|
|
|
|
constexpr char FILE_SEPARATOR = '/';
|
|
|
|
class Line {
|
|
public:
|
|
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;
|
|
m_pid = pid;
|
|
m_uid = uid;
|
|
assert(m_pid >= 0);
|
|
}
|
|
|
|
void show(int row, unsigned int proglen, unsigned int devlen);
|
|
void log();
|
|
|
|
double sent_value;
|
|
double recv_value;
|
|
const char *devicename;
|
|
|
|
private:
|
|
const char *m_name;
|
|
const char *m_cmdline;
|
|
pid_t m_pid;
|
|
uid_t m_uid;
|
|
};
|
|
|
|
#include <sstream>
|
|
|
|
std::string itoa(int i) {
|
|
std::stringstream out;
|
|
out << i;
|
|
return out.str();
|
|
}
|
|
|
|
/**
|
|
* @returns the username that corresponds to this uid
|
|
*/
|
|
std::string uid2username(uid_t uid) {
|
|
struct passwd *pwd = NULL;
|
|
errno = 0;
|
|
|
|
/* points to a static memory area, should not be freed */
|
|
pwd = getpwuid(uid);
|
|
|
|
if (pwd == NULL)
|
|
if (errno == 0)
|
|
return itoa(uid);
|
|
else
|
|
forceExit(false, "Error calling getpwuid(3) for uid %d: %d %s", uid,
|
|
errno, strerror(errno));
|
|
else
|
|
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 text will be rendered up to max_len - 2
|
|
* characters and then ".." will be rendered.
|
|
*/
|
|
static void mvaddstr_truncate_trailing(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 {
|
|
mvaddnstr(row, col, str, max_len - 2);
|
|
addstr("..");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
if (showBasename) {
|
|
if (index(progname, FILE_SEPARATOR) != NULL) {
|
|
progname = rindex(progname, FILE_SEPARATOR) + 1;
|
|
}
|
|
}
|
|
|
|
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, unsigned int devlen) {
|
|
assert(m_pid >= 0);
|
|
assert(m_pid <= PID_MAX);
|
|
|
|
const int column_offset_pid = 0;
|
|
const int column_offset_user = column_offset_pid + COLUMN_WIDTH_PID + 1;
|
|
const int column_offset_program = column_offset_user + COLUMN_WIDTH_USER + 1;
|
|
const int column_offset_dev = column_offset_program + proglen + 2;
|
|
const int column_offset_sent = column_offset_dev + devlen + 1;
|
|
const int column_offset_received = column_offset_sent + COLUMN_WIDTH_SENT + 1;
|
|
const int column_offset_unit =
|
|
column_offset_received + COLUMN_WIDTH_RECEIVED + 1;
|
|
|
|
// PID column
|
|
if (m_pid == 0)
|
|
mvaddch(row, column_offset_pid + COLUMN_WIDTH_PID - 1, '?');
|
|
else
|
|
mvprintw(row, column_offset_pid, COLUMN_FORMAT_PID, m_pid);
|
|
|
|
std::string username = uid2username(m_uid);
|
|
mvaddstr_truncate_trailing(row, column_offset_user, username.c_str(),
|
|
username.size(), COLUMN_WIDTH_USER);
|
|
|
|
mvaddstr_truncate_cmdline(row, column_offset_program, m_name, m_cmdline,
|
|
proglen);
|
|
|
|
mvaddstr(row, column_offset_dev, devicename);
|
|
|
|
mvprintw(row, column_offset_sent, COLUMN_FORMAT_SENT, sent_value);
|
|
|
|
mvprintw(row, column_offset_received, COLUMN_FORMAT_RECEIVED, recv_value);
|
|
mvaddstr(row, column_offset_unit, desc_view_mode[viewMode]);
|
|
}
|
|
|
|
void Line::log() {
|
|
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 get_devlen(Line *lines[], int nproc, int rows) {
|
|
int devlen = MIN_COLUMN_WIDTH_DEV;
|
|
int curlen;
|
|
for (int i = 0; i < nproc; i++) {
|
|
if (i + 3 < rows) {
|
|
curlen = strlen(lines[i]->devicename);
|
|
if (curlen > devlen)
|
|
curlen = devlen;
|
|
}
|
|
}
|
|
|
|
if (devlen > MAX_COLUMN_WIDTH_DEV)
|
|
devlen = MAX_COLUMN_WIDTH_DEV;
|
|
|
|
return devlen;
|
|
}
|
|
|
|
int GreatestFirst(const void *ma, const void *mb) {
|
|
Line **pa = (Line **)ma;
|
|
Line **pb = (Line **)mb;
|
|
Line *a = *pa;
|
|
Line *b = *pb;
|
|
double aValue;
|
|
if (sortRecv) {
|
|
aValue = a->recv_value;
|
|
} else {
|
|
aValue = a->sent_value;
|
|
}
|
|
|
|
double bValue;
|
|
if (sortRecv) {
|
|
bValue = b->recv_value;
|
|
} else {
|
|
bValue = b->sent_value;
|
|
}
|
|
|
|
if (aValue > bValue) {
|
|
return -1;
|
|
}
|
|
if (aValue == bValue) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void init_ui() {
|
|
WINDOW *screen = initscr();
|
|
cursOrig = curs_set(0);
|
|
raw();
|
|
noecho();
|
|
cbreak();
|
|
nodelay(screen, TRUE);
|
|
caption = new std::string("NetHogs");
|
|
caption->append(getVersion());
|
|
// caption->append(", running at ");
|
|
}
|
|
|
|
void exit_ui() {
|
|
clear();
|
|
endwin();
|
|
delete caption;
|
|
if (cursOrig != ERR)
|
|
curs_set(cursOrig);
|
|
}
|
|
|
|
void ui_tick() {
|
|
switch (getch()) {
|
|
case 'q':
|
|
/* quit */
|
|
quit_cb(0);
|
|
break;
|
|
case 's':
|
|
/* sort on 'sent' */
|
|
sortRecv = false;
|
|
break;
|
|
case 'r':
|
|
/* 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;
|
|
break;
|
|
case 'b':
|
|
/* show only the process basename */
|
|
showBasename = !showBasename;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void show_trace(Line *lines[], int nproc) {
|
|
std::cout << "\nRefreshing:\n";
|
|
|
|
/* print them */
|
|
for (int i = 0; i < nproc; i++) {
|
|
lines[i]->log();
|
|
delete lines[i];
|
|
}
|
|
|
|
/* print the 'unknown' connections, for debugging */
|
|
for (auto it = unknowntcp->connections.begin(); it != unknowntcp->connections.end(); ++it) {
|
|
std::cout << "Unknown connection: "
|
|
<< (*it)->refpacket->gethashstring()
|
|
<< std::endl;
|
|
}
|
|
}
|
|
|
|
void show_ncurses(Line *lines[], int nproc) {
|
|
int rows; // number of terminal rows
|
|
int cols; // number of terminal columns
|
|
unsigned int proglen; // max length of the "PROGRAM" column
|
|
|
|
double sent_global = 0;
|
|
double recv_global = 0;
|
|
|
|
getmaxyx(stdscr, rows, cols); /* find the boundaries of the screen */
|
|
|
|
if (cols < 62) {
|
|
erase();
|
|
mvprintw(0, 0,
|
|
"The terminal is too narrow! Please make it wider.\nI'll wait...");
|
|
return;
|
|
}
|
|
|
|
if (cols > PROGNAME_WIDTH)
|
|
cols = PROGNAME_WIDTH;
|
|
|
|
// issue #110 - maximum devicename length min=5, max=15
|
|
int devlen = get_devlen(lines, nproc, rows);
|
|
|
|
proglen = cols - 50 - devlen;
|
|
|
|
erase();
|
|
mvprintw(0, 0, "%s", caption->c_str());
|
|
attron(A_REVERSE);
|
|
mvprintw(2, 0,
|
|
" PID USER %-*.*s %-*.*s SENT RECEIVED ",
|
|
proglen, proglen, "PROGRAM", devlen, devlen, "DEV");
|
|
attroff(A_REVERSE);
|
|
|
|
/* print them */
|
|
int i;
|
|
for (i = 0; i < nproc; i++) {
|
|
if (i + 3 < rows)
|
|
lines[i]->show(i + 3, proglen, devlen);
|
|
recv_global += lines[i]->recv_value;
|
|
sent_global += lines[i]->sent_value;
|
|
delete lines[i];
|
|
}
|
|
attron(A_REVERSE);
|
|
int totalrow = std::min(rows - 1, 3 + 1 + i);
|
|
mvprintw(totalrow, 0, " TOTAL %-*.*s %-*.*s %11.3f %11.3f ",
|
|
proglen, proglen, "", devlen, devlen, "", sent_global, recv_global);
|
|
mvprintw(3 + 1 + i, cols - COLUMN_WIDTH_UNIT, "%s", desc_view_mode[viewMode]);
|
|
attroff(A_REVERSE);
|
|
mvprintw(totalrow + 1, 0, "%s", "");
|
|
refresh();
|
|
}
|
|
|
|
// Display all processes and relevant network traffic using show function
|
|
void do_refresh() {
|
|
refreshconninode();
|
|
refreshcount++;
|
|
|
|
if (viewMode == VIEWMODE_KBPS || viewMode == VIEWMODE_MBPS ||
|
|
viewMode == VIEWMODE_GBPS) {
|
|
remove_timed_out_processes();
|
|
}
|
|
|
|
ProcList *curproc = processes;
|
|
int nproc = processes->size();
|
|
|
|
/* initialize to null pointers */
|
|
Line *lines[nproc];
|
|
for (int i = 0; i < nproc; i++)
|
|
lines[i] = NULL;
|
|
|
|
int n = 0;
|
|
|
|
while (curproc != NULL) {
|
|
// walk though its connections, summing up their data, and
|
|
// throwing away connections that haven't received a package
|
|
// in the last CONNTIMEOUT seconds.
|
|
assert(curproc->getVal() != NULL);
|
|
assert(nproc == processes->size());
|
|
|
|
float value_sent = 0, value_recv = 0;
|
|
|
|
if (viewMode == VIEWMODE_KBPS) {
|
|
curproc->getVal()->getkbps(&value_recv, &value_sent);
|
|
} else if (viewMode == VIEWMODE_MBPS) {
|
|
curproc->getVal()->getmbps(&value_recv, &value_sent);
|
|
} else if (viewMode == VIEWMODE_GBPS) {
|
|
curproc->getVal()->getgbps(&value_recv, &value_sent);
|
|
} else if (viewMode == VIEWMODE_TOTAL_KB) {
|
|
curproc->getVal()->gettotalkb(&value_recv, &value_sent);
|
|
} else if (viewMode == VIEWMODE_TOTAL_MB) {
|
|
curproc->getVal()->gettotalmb(&value_recv, &value_sent);
|
|
} else if (viewMode == VIEWMODE_TOTAL_B) {
|
|
curproc->getVal()->gettotalb(&value_recv, &value_sent);
|
|
} else {
|
|
forceExit(false, "Invalid viewMode: %d", viewMode);
|
|
}
|
|
uid_t uid = curproc->getVal()->getUid();
|
|
assert(curproc->getVal()->pid >= 0);
|
|
assert(n < nproc);
|
|
|
|
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++;
|
|
}
|
|
|
|
/* sort the accumulated lines */
|
|
qsort(lines, nproc, sizeof(Line *), GreatestFirst);
|
|
|
|
if (tracemode || DEBUG)
|
|
show_trace(lines, nproc);
|
|
else
|
|
show_ncurses(lines, nproc);
|
|
|
|
if (refreshlimit != 0 && refreshcount >= refreshlimit)
|
|
quit_cb(0);
|
|
}
|