4
\$\begingroup\$

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 };
}
\$\endgroup\$
5
  • \$\begingroup\$ Some improvements I already thought of: Making sure the reading doesnt fail in BandwidthMonitor ctor (in addition to checking the file); Count Time as absolute number of days, not as time the program was running. \$\endgroup\$
    – smoothware
    Commented Sep 12, 2016 at 13:26
  • \$\begingroup\$ Program will currently crash when going into energy save mode (when I close the laptop). Working on how to fix that atm. \$\endgroup\$
    – smoothware
    Commented Sep 12, 2016 at 16:17
  • \$\begingroup\$ using static member variables for shared data might not be the best option, I am currently still researching this (shared_ptr<>? ...); Also, every thread adds its time to the Time variable, so if I have say 3 threads running, the program will run 3 times as fast as time in reality. The solution seems to be only tracking time in one, separate place. \$\endgroup\$
    – smoothware
    Commented Sep 13, 2016 at 16:02
  • \$\begingroup\$ Matter of fact, I just realized all I have to really do is save the time point the program was first run (e.g. if there is no time point saved yet) and then always calc the difference to current time point... \$\endgroup\$
    – smoothware
    Commented Sep 13, 2016 at 16:26
  • \$\begingroup\$ Another point that was made is BandwidthMonitor class being unnecessary: The functionality of run() could be implemented as a free standing function. \$\endgroup\$
    – smoothware
    Commented Sep 13, 2016 at 21:02

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.