/*************************************************************************** main.cpp ------------------- begin : Wed Jul 25 2001 copyright : (C) 2001 - 2012 by Roland Riegel email : feedback@roland-riegel.de ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ /* * nload * real time monitor for network traffic * Copyright (C) 2001 - 2012 Roland Riegel * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H #include #endif #include "device.h" #include "devreader.h" #include "devreaderfactory.h" #include "graph.h" #include "main.h" #include "screen.h" #include "setting.h" #include "settingfilter.h" #include "settingstore.h" #include #include #include #include #include #include #include #include #include #include #undef clear #undef erase #undef refresh #define STANDARD_AVERAGE_WINDOW 300 #define STANDARD_DATA_FORMAT Statistics::humanReadableByte #define STANDARD_HIDE_GRAPHS false #define STANDARD_MAX_DEFLECTION 10240 #define STANDARD_REFRESH_INTERVAL 500 #define STANDARD_TRAFFIC_FORMAT Statistics::humanReadableBit using namespace std; static OptWindow m_optWindow; static TrafficWindow m_mainWindow; static bool quit = false; int main(int argc, char *argv[]) { SettingStore::add(Setting("AverageWindow", "Window length for average (s)", STANDARD_AVERAGE_WINDOW)); SettingStore::add(Setting("BarMaxIn", "Max Incoming deflection (kBit/s)", STANDARD_MAX_DEFLECTION)); SettingStore::add(Setting("BarMaxOut", "Max Outgoing deflection (kBit/s)", STANDARD_MAX_DEFLECTION)); SettingStore::add(Setting("DataFormat", "Unit for data numbers", STANDARD_DATA_FORMAT)); SettingStore::add(Setting("Devices", "Devices to show", "all")); SettingStore::add(Setting("MultipleDevices", "Show multiple devices", STANDARD_HIDE_GRAPHS)); SettingStore::add(Setting("RefreshInterval", "Refresh interval (ms)", STANDARD_REFRESH_INTERVAL)); SettingStore::add(Setting("TrafficFormat", "Unit for traffic numbers", STANDARD_TRAFFIC_FORMAT)); SettingStore::get("AverageWindow").pushFilter(new SettingFilterMin(5)); SettingStore::get("BarMaxIn").pushFilter(new SettingFilterMin(10)); SettingStore::get("BarMaxOut").pushFilter(new SettingFilterMin(10)); SettingStore::get("RefreshInterval").pushFilter(new SettingFilterMin(50)); SettingStore::get("Devices").pushFilter(new SettingFilterDefault("all")); SettingStore::get("Devices").pushFilter(new SettingFilterExclusive("all")); map valueMapping; valueMapping[toString(false)] = "[ ]"; valueMapping[toString(true)] = "[x]"; SettingStore::get("MultipleDevices").pushFilter(new SettingFilterMap(valueMapping)); valueMapping.clear(); valueMapping[toString(Statistics::humanReadableBit)] = "Human Readable (Bit)"; valueMapping[toString(Statistics::humanReadableByte)] = "Human Readable (Byte)"; valueMapping[toString(Statistics::bit)] = "Bit"; valueMapping[toString(Statistics::byte)] = "Byte"; valueMapping[toString(Statistics::kiloBit)] = "kBit"; valueMapping[toString(Statistics::kiloByte)] = "kByte"; valueMapping[toString(Statistics::megaBit)] = "MBit"; valueMapping[toString(Statistics::megaByte)] = "MByte"; valueMapping[toString(Statistics::gigaBit)] = "GBit"; valueMapping[toString(Statistics::gigaByte)] = "GByte"; SettingStore::get("TrafficFormat").pushFilter(new SettingFilterMap(valueMapping)); SettingStore::get("DataFormat").pushFilter(new SettingFilterMap(valueMapping)); valueMapping.clear(); // retrieve home directory const char* homeDirArray = getenv("HOME"); if(!homeDirArray) { cerr << "Could not retrieve home directory." << endl; exit(1); } string homeDir = homeDirArray; SettingStore::readFromFile(SYSCONFDIR "/nload.conf"); SettingStore::readFromFile(homeDir + "/.nload"); // parse the command line bool deleteDevicesRequested = true; for(int i = 1; i < argc; i++) { // does the user want help? if(strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { printHelp(false); exit(0); } // has the user set a non-default average time window? else if(strcmp(argv[i], "-a") == 0) { Setting& setting = SettingStore::get("AverageWindow"); if(i < argc - 1 && isdigit(argv[ i + 1 ][0]) != 0) { setting = atoi(argv[ i + 1 ]); if(setting < 1) setting = STANDARD_AVERAGE_WINDOW; i++; } else { cerr << "Wrong argument for the -a parameter." << endl; printHelp(true); exit(1); } } // has the user set a non-default 100% mark for // the incoming bandwidth bar? else if(strcmp(argv[i], "-i") == 0) { Setting& setting = SettingStore::get("BarMaxIn"); if(i < argc - 1 && isdigit(argv[ i + 1 ][0]) != 0) { setting = atol(argv[ i + 1 ]); if(setting == 0) setting = STANDARD_MAX_DEFLECTION; i++; } else { cerr << "Wrong argument for the -i parameter." << endl; printHelp(true); exit(1); } } // has the user set a non-default 100% mark for // the outgoing bandwidth bar? else if(strcmp(argv[i], "-o") == 0) { Setting& setting = SettingStore::get("BarMaxOut"); if(i < argc - 1 && isdigit(argv[ i + 1 ][0]) != 0) { setting = atol(argv[ i + 1 ]); if(setting == 0) setting = STANDARD_MAX_DEFLECTION; i++; } else { cerr << "Wrong argument for the -o parameter." << endl; printHelp(true); exit(1); } } // has the user set a non-default refresh interval? else if(strcmp(argv[i], "-t") == 0) { Setting& setting = SettingStore::get("RefreshInterval"); if(i < argc - 1 && isdigit(argv[ i + 1 ][0]) != 0) { setting = atoi(argv[ i + 1 ]); if(setting == 0) setting = STANDARD_REFRESH_INTERVAL; i++; } else { cerr << "Wrong argument for the -t parameter." << endl; printHelp(true); exit(1); } } // has the user set a non-default unit for traffic numbers? else if(strcmp(argv[i], "-u") == 0) { Setting& setting = SettingStore::get("TrafficFormat"); if(i < argc - 1 && isalpha(argv[ i + 1 ][0]) != 0) { switch(argv[ i + 1 ][0]) { case 'H': setting = Statistics::humanReadableByte; break; case 'h': setting = Statistics::humanReadableBit; break; case 'B': setting = Statistics::byte; break; case 'b': setting = Statistics::bit; break; case 'K': setting = Statistics::kiloByte; break; case 'k': setting = Statistics::kiloBit; break; case 'M': setting = Statistics::megaByte; break; case 'm': setting = Statistics::megaBit; break; case 'G': setting = Statistics::gigaByte; break; case 'g': setting = Statistics::gigaBit; break; default: cerr << "Wrong argument for the -u parameter." << endl; printHelp(true); exit(1); } i++; } else { cerr << "Wrong argument for the -u parameter." << endl; printHelp(true); exit(1); } } // has the user set a non-default unit for numbers of amount of data? else if(strcmp(argv[i], "-U") == 0) { Setting& setting = SettingStore::get("DataFormat"); if(i < argc - 1 && isalpha(argv[ i + 1 ][0]) != 0) { switch(argv[ i + 1 ][0]) { case 'H': setting = Statistics::humanReadableByte; break; case 'h': setting = Statistics::humanReadableBit; break; case 'B': setting = Statistics::byte; break; case 'b': setting = Statistics::bit; break; case 'K': setting = Statistics::kiloByte; break; case 'k': setting = Statistics::kiloBit; break; case 'M': setting = Statistics::megaByte; break; case 'm': setting = Statistics::megaBit; break; case 'G': setting = Statistics::gigaByte; break; case 'g': setting = Statistics::gigaBit; break; default: cerr << "Wrong argument for the -U parameter." << endl; printHelp(true); exit(1); } i++; } else { cerr << "Wrong argument for the -U parameter." << endl; printHelp(true); exit(1); } } // has the user chosen to display multiple devices and thus not to display graphs? else if(strcmp(argv[i], "-m") == 0) { SettingStore::get("MultipleDevices") = true; } // obsolete -b option else if(strcmp(argv[i], "-b") == 0) { } // obsolete -s option else if(strcmp(argv[i], "-s") == 0) { } // assume unknown parameter to be the network device else { Setting& devices = SettingStore::get("Devices"); if(deleteDevicesRequested) { devices = ""; deleteDevicesRequested = false; } devices.setThroughFilter(trim(devices.getThroughFilter() + " " + argv[i])); } } try { // auto-detect network devices DevReaderFactory::findAllDevices(); } catch (std::runtime_error e) { cerr << "Cannot find any device: " << e.what() << endl; return -1; }; const map& deviceReaders = DevReaderFactory::getAllDevReaders(); // create one instance of the Device class per device map deviceHandlers; for(map::const_iterator itDevice = deviceReaders.begin(); itDevice != deviceReaders.end(); ++itDevice) { Device* device = new Device(*itDevice->second); device->update(); deviceHandlers[itDevice->first] = device; } init(); while(!quit) { // wait RefreshInterval milliseconds (in steps of 100 ms) struct timespec wantedTime; wantedTime.tv_sec = 0; int restOfRefreshInterval = SettingStore::get("RefreshInterval"); while(restOfRefreshInterval > 0 && !quit) { restOfRefreshInterval -= 100; wantedTime.tv_nsec = (restOfRefreshInterval >= 0 ? 100 : 100 + restOfRefreshInterval) * 1000000L; nanosleep(&wantedTime, 0); // process keyboard int key; while((key = getch()) != ERR) { if(m_optWindow.isVisible()) { if(key == KEY_F(2)) { m_optWindow.hide(); m_mainWindow.resize(0, 0, Screen::width(), Screen::height()); restOfRefreshInterval = 0; // update the screen } else { m_optWindow.processKey(key); } } else { switch(key) { case KEY_F(2): m_mainWindow.resize(0, Screen::height() / 4, Screen::width(), Screen::height() - Screen::height() / 4); m_optWindow.show(0, 0, Screen::width(), Screen::height() / 4); restOfRefreshInterval = 0; // update the screen break; case KEY_F(5): SettingStore::writeToFile(homeDir + "/.nload"); break; case KEY_F(6): SettingStore::readFromFile("/etc/nload.conf"); SettingStore::readFromFile(homeDir + "/.nload"); break; case 'q': case 'Q': quit = true; break; default: m_mainWindow.processKey(key); } } } } if(quit) break; vector devicesRequested = split(trim(SettingStore::get("Devices").getThroughFilter()), " "); vector devicesToShow; if(!devicesRequested.empty() && devicesRequested.front() != "all") { // check if requested devices are available for(vector::const_iterator itRequested = devicesRequested.begin(); itRequested != devicesRequested.end(); ++itRequested) { map::const_iterator itDetectedDevice = deviceHandlers.find(*itRequested); if(itDetectedDevice != deviceHandlers.end()) devicesToShow.push_back(itDetectedDevice->second); } } if(devicesToShow.empty()) { // use all detected devices for(map::const_iterator itDevice = deviceHandlers.begin(); itDevice != deviceHandlers.end(); ++itDevice) devicesToShow.push_back(itDevice->second); } // enumerate devices for display unsigned int deviceIndex = 0; for(vector::const_iterator itDevice = devicesToShow.begin(); itDevice != devicesToShow.end(); ++itDevice) { (*itDevice)->setDeviceNumber(deviceIndex++); (*itDevice)->setTotalNumberOfDevices(devicesToShow.size()); } // update all devices for(map::const_iterator itDevice = deviceHandlers.begin(); itDevice != deviceHandlers.end(); ++itDevice) itDevice->second->update(); // clear the screen m_mainWindow.clear(); // print device data m_mainWindow.printTraffic(devicesToShow); // refresh the screen m_mainWindow.refresh(); if(m_optWindow.isVisible()) m_optWindow.refresh(); // always show cursor in option dialog } finish(); for(map::const_iterator itDevice = deviceHandlers.begin(); itDevice != deviceHandlers.end(); ++itDevice) delete itDevice->second; deviceHandlers.clear(); return 0; } void init() { // handle interrrupt signal signal(SIGINT, end); signal(SIGTERM, end); signal(SIGWINCH, terminalResized); // initialize ncurses initscr(); keypad(stdscr, true); nodelay(stdscr, true); noecho(); nonl(); cbreak(); // create main window m_mainWindow.show(0, 0, Screen::width(), Screen::height()); } void finish() { // destroy main window m_mainWindow.hide(); // stop ncurses endwin(); } void end(int signal) { quit = true; } void terminalResized(int signal) { bool optWindowWasVisible = m_optWindow.isVisible(); m_optWindow.hide(); finish(); init(); if(optWindowWasVisible) { m_mainWindow.resize(0, Screen::height() / 4, Screen::width(), Screen::height() - Screen::height() / 4); m_optWindow.show(0, 0, Screen::width(), Screen::height() / 4); } } void printHelp(bool error) { // print disclaimer (error ? cerr : cout) << "\n" << PACKAGE << " version " << VERSION << "\n" << "Copyright (C) 2001 - 2012 by Roland Riegel \n" << PACKAGE << " comes with ABSOLUTELY NO WARRANTY. This is free software, and you are\n" << "welcome to redistribute it under certain conditions. For more details see the\n" << "GNU General Public License Version 2 (http://www.gnu.org/copyleft/gpl.html).\n\n" << "Command line syntax:\n" << PACKAGE << " [options] [devices]\n" << PACKAGE << " --help|-h\n\n" << "Options:\n" << "-a period Sets the length in seconds of the time window for average\n" << " calculation.\n" << " Default is " << STANDARD_AVERAGE_WINDOW << ".\n" << "-i max_scaling Specifies the 100% mark in kBit/s of the graph indicating the\n" << " incoming bandwidth usage. Ignored if max_scaling is 0 or the\n" << " switch -m is given.\n" << " Default is " << STANDARD_MAX_DEFLECTION << ".\n" << "-m Show multiple devices at a time; no traffic graphs.\n" << "-o max_scaling Same as -i but for the graph indicating the outgoing bandwidth\n" << " usage.\n" << " Default is " << STANDARD_MAX_DEFLECTION << ".\n" << "-t interval Determines the refresh interval of the display in milliseconds.\n" << " Default is " << STANDARD_REFRESH_INTERVAL << ".\n" << "-u h|b|k|m|g Sets the type of unit used for the display of traffic numbers.\n" << " H|B|K|M|G h: auto, b: Bit/s, k: kBit/s, m: MBit/s etc.\n" << " H: auto, B: Byte/s, K: kByte/s, M: MByte/s etc.\n" << " Default is h.\n" << "-U h|b|k|m|g Same as -u, but for a total amount of data (without \"/s\").\n" << " H|B|K|M|G Default is H.\n" << "devices Network devices to use.\n" << " Default is to use all auto-detected devices.\n" << "--help\n" << "-h Print this help.\n\n" << "example: " << PACKAGE << " -t 200 -i 1024 -o 128 -U M\n\n" << "The options above can also be changed at run time by pressing the 'F2' key.\n" << endl; }