/* * 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 #include #include #include #include #include #include #include "nethogs.h" #include "process.h" #include 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 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); }