I have written a very basic bandwidth usage monitor in C++/winpcap.
I would like to get some feedback, regarding design choices, implementation, style, correctness. (Or anything else you care to mention for that matter).
Win TaskManager schows 0% CPU usage and 1,7MB RAM, and the program runs and updates the files as expected on my machine.
Because it consists of a couple 100 lines of code, I decided to host the code on GitHub for viewing: https://github.com/Raincode/NetworkMonitor
Short Description:
Saves bytes used in "bytes.txt"
Saves timespan for bytes used in "time.txt"
Saves a "statistic" in "usage.txt"
My goal was this: I want to open "usage.txt" and see how much data I used over the internet in a certain time period. The program is intended to be started up when the computer starts, and run all the time in the background.
I hope I came to the right place and someone will be able to help me.
P.S.: The linux support is experimental, e.g. I haven't compiled it on a linux machine yet.
Code (removed boilerplate)
- PcapAdapter is a wrapper for pcap_if*
- PcapDevices is a wrapper for pcap_if_t*
main.cpp:
PcapDevices d;
auto alldevs = d.get_devices();
std::vector<BandwidthMonitor> monitors;
for (const auto& d : alldevs) {
if (!d->flags & PCAP_IF_LOOPBACK) {
monitors.emplace_back(d);
}
}
// thread for each device which will open a pcap_if to track bandwidth usage
std::vector<std::thread> monitor_threads;
for (const auto& m : monitors) {
monitor_threads.emplace_back(&BandwidthMonitor::run, m);
}
for (auto& t : monitor_threads) {
t.join();
}
BandwidthMonitor.hpp:
class BandwidthMonitor {
public:
BandwidthMonitor(pcap_if_t* device);
// to be run in a seperate thread for each BandwidthMonitor
void run();
private:
// saves Bytes and Time to a file, along with a usage statistic
void save_snapshot() const;
void update_snapshot(std::chrono::seconds& acc_time);
private:
// static so multiple instances accumulate to same variable
static unsigned long long Bytes;
static std::chrono::seconds Time;
static std::mutex Mutex;
// used for saving snapshots of the accumulated values
// after a certain time period
std::chrono::system_clock::time_point m_time_point;
std::chrono::seconds m_snapshot_time { 30 };
const std::string bytefile { "bytes.txt" };
const std::string timefile { "time.txt" };
// so run() can open the pcap_of for the device
pcap_if_t* m_device;
// the pcap_if used to track bandwidth usage
PcapAdapter m_adapter;
};
BandwidthMonitor.cpp
unsigned long long BandwidthMonitor::Bytes {};
std::chrono::seconds BandwidthMonitor::Time { 0 };
std::mutex BandwidthMonitor::Mutex {};
BandwidthMonitor::BandwidthMonitor(pcap_if_t* device)
:m_device { device }
{
// Initializes previous values from files if Bytes and Time are 0
// This is intended to run once at the start of the program (when they are 0)
if (!Bytes) {
static std::mutex is_mutex;
std::lock_guard<std::mutex> guard(is_mutex);
std::ifstream is(bytefile);
if (is) {
unsigned long long b;
is >> b;
Bytes = b;
}
}
if (!Time.count()) {
static std::mutex is_mutex;
std::lock_guard<std::mutex> guard(is_mutex);
std::ifstream is(timefile);
if (is) {
std::chrono::seconds::rep sec;
is >> sec;
Time = std::chrono::seconds(sec);
}
}
}
void BandwidthMonitor::run()
{
m_adapter.open_live(m_device);
m_time_point = std::chrono::system_clock::now();
pcap_pkthdr* header {};
const u_char* data;
int res {};
std::chrono::seconds acc_time {};
while ((res = pcap_next_ex(m_adapter.get(), &header, &data) >= 0)) {
if (!res) continue; // timeout
std::lock_guard<std::mutex> guard(Mutex);
Bytes += header->caplen;
update_snapshot(acc_time);
}
}
void BandwidthMonitor::update_snapshot(std::chrono::seconds& acc_time)
{
// bugfix (would crash locking twice)
//std::lock_guard<std::mutex> guard(Mutex);
auto then = m_time_point;
m_time_point = std::chrono::system_clock::now();
acc_time += std::chrono::duration_cast<std::chrono::seconds>(m_time_point - then);
if (acc_time >= m_snapshot_time) {
Time += m_snapshot_time;
acc_time -= m_snapshot_time;
save_snapshot();
}
}
void BandwidthMonitor::save_snapshot() const
{
static std::mutex os_mutex;
std::lock_guard<std::mutex> lk(os_mutex);
std::ofstream osbytes(bytefile);
osbytes << Bytes;
std::ofstream osttime(timefile);
osttime << Time.count();
auto byteval = convert_bytes(Bytes);
std::ofstream osusage("usage.txt");
osusage << byteval.first << byteval.second
<< " in the past "
<< std::chrono::duration_cast<std::chrono::hours>(Time).count() / 24
<< " days.\n";
}
/**
* \brief Divides value of bytes by 1024, until a number >= 1 is reached
* \return Returns a std::pair: first=new byte value, second=corresponding unit
*/
std::pair<unsigned long long, std::string> convert_bytes(unsigned long long bytes)
{
// convert to most convenient unit
auto count = 0;
auto value = bytes;
while (value / BytesPerKilo >= 1) {
++count;
value /= BytesPerKilo;
}
std::string unit;
switch (count) {
case 0: unit = "bytes"; break;
case 1: unit = "KB"; break;
case 2: unit = "MB"; break;
default: unit = "GB"; break;
}
return { value, unit };
}